Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch from class-star to nclasses. #2784

Merged
merged 10 commits into from
Mar 17, 2023
Merged

Switch from class-star to nclasses. #2784

merged 10 commits into from
Mar 17, 2023

Conversation

Ambrevar
Copy link
Member

@Ambrevar Ambrevar commented Feb 10, 2023

Description

Switches from class-star to https://github.com/atlas-engineer/nclasses.

News:

  • No more need for the accessor transformer.
  • Which saves some 750 lines of code! O.o
  • Fixes a bunch of things.
    • In particular: when the slot whas inherited, the accessor was created in the wrong package.

Fixes #1102.

Discussion

  • For inherited slots with no accessor, we now need to specify :accessor nil. See prompter:active-attributes-keys for instance. It makes sense, but it's a bit unwieldy. Can we do better?

To do:

  • Package nclasses for Guix.

Checklist:

Everything in this checklist is required for each PR. Please do not approve a PR that does not have all of these items.

  • I have pulled from master before submitting this PR
  • There are no merge conflicts.
  • I've added the new dependencies as:
    • ASDF dependencies,
    • Git submodules,
      cd /path/to/nyxt/checkout
      git submodule add https://gitlab.common-lisp.net/nyxt/py-configparser _build/py-configparser
    • and Guix dependencies.
  • My code follows the style guidelines for Common Lisp code. See:
  • I have performed a self-review of my own code.
  • My code has been reviewed by at least one peer. (The peer review to approve a PR counts. The reviewer must download and test the code.)
  • Documentation:
    • All my code has docstrings and :documentations written in the aforementioned style. (It's OK to skip the docstring for really trivial parts.)
    • I have updated the existing documentation to match my changes.
    • I have commented my code in hard-to-understand areas.
    • I have updated the changelog.lisp with my changes if it's anything user-facing (new features, important bug fix, compatibility breakage).
    • I have added a migration.lisp entry for all compatibility-breaking changes.
    • (If this changes something about the features showcased on Nyxt website) I have these changes described in the new/existing article at Nyxt website or will notify one of maintainters to do so.
  • Compilation and tests:
    • My changes generate no new warnings.
    • I have added tests that prove my fix is effective or that my feature works. (If possible.)
    • New and existing unit tests pass locally with my changes.

@Ambrevar Ambrevar force-pushed the switch-to-nclasses branch 2 times, most recently from 8fbf480 to fee7d5c Compare February 10, 2023 14:56
@Ambrevar
Copy link
Member Author

I like the result, but curious what you think, @aartaka @aadcg

@aartaka
Copy link
Contributor

aartaka commented Feb 13, 2023

I'll probably review after type inference PR is merged, so that we don't do it twice へ(゜∇、°)へ

@Ambrevar
Copy link
Member Author

Type inference should be merged in nclasses (see atlas-engineer/nclasses#5), so it won't disturb this PR which can be review now I believe.

Copy link
Contributor

@aartaka aartaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good, except that :accessor nil spilt everywhere (and not spilt somewhere it probably should be consistently spilt, like in prompter:constructor). Maybe inherit accessors from superclasses, if present? Otherwise it feels like yet another syntactical trap.

libraries/theme/theme.lisp Show resolved Hide resolved
@@ -1302,7 +1290,8 @@ proceeding."
(prompter:enable-marks-p t)
(prompter:actions-on-return (list (lambda-unmapped-command set-current-buffer)
(lambda-mapped-command buffer-delete)
'reload-buffers))
'reload-buffers)
:accessor nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is weird. Should we maybe not generate accessor if it's present already? We pull moptilities in already, so why not use it for accessor inspection too?

source/command-commands.lisp Outdated Show resolved Hide resolved
@@ -253,8 +252,7 @@ It's the complement of `nyxt-packages' and `nyxt-user-packages'."
((name nil
:type (or symbol null))
(class-sym nil
:type (or symbol null)))
(:accessor-name-transformer (class*:make-name-transformer name)))
:type (or symbol null))))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then, nil is a symbol too. However hacky it is :P

@@ -27,7 +27,8 @@
((prompter:name "Quicklisp systems")
(prompter:constructor (mapcar #'ql-dist:short-description (ql:system-list)))
(prompter:actions-on-return (lambda-command quickload* (systems)
(ql:quickload (first systems))))))
(ql:quickload (first systems)))
:accessor nil)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's alarming yet again...

@Ambrevar
Copy link
Member Author

Accessors cannot easily be inherited, because unlike the situation with types, generating accessors has a global side effect. Generating the wrong accessor would be an important bug.

The issue is the same with hu.dwim.defclass-star. If you think about it, it's normal to say "do not generate an accessor for inherited slots", because the global class option is otherwise to generate accessors for all slots.

There is no way to infer that the user does not effectively want to generate the accessor from the inherited slot, as "package polluting" as it might be.

@aartaka
Copy link
Contributor

aartaka commented Feb 13, 2023

Accessors cannot easily be inherited, because unlike the situation with types, generating accessors has a global side effect. Generating the wrong accessor would be an important bug.

The issue is the same with hu.dwim.defclass-star. If you think about it, it's normal to say "do not generate an accessor for inherited slots", because the global class option is otherwise to generate accessors for all slots.

There is no way to infer that the user does not effectively want to generate the accessor from the inherited slot, as "package polluting" as it might be.

But then, if the package is different, then the slot is most likely inherited, right? For those, we most probably, should not generate anything. It's a hacky heyristic, but a reasonable one, isn't it?

@aartaka
Copy link
Contributor

aartaka commented Feb 17, 2023

@Ambrevar, CI was failing because closer-mop:class-finalized-p was called on NIL in nclasses. Should be fixed in atlas-engineer/nclasses@f718b16. Mind updating?

@Ambrevar
Copy link
Member Author

Done, thanks for catching the bug!

@aartaka
Copy link
Contributor

aartaka commented Feb 21, 2023

Oh, sems like some accessors are not generated, failing the CI...

@Ambrevar
Copy link
Member Author

Ambrevar commented Mar 2, 2023

I've investigated this and there are multiple issues.

  1. dcfd35c is logical, but I don't understand how it used to pass previously.

  2. 5c2a395 is an unfortunate workaround. Still much better than 8ee001d.

Issue 2 is a fundamental problem with CL and CLOS. Let me describe it:

  • parent package exports foo.
  • In the child package that :uses parent, there is a class with a foo slot. (find-symbol "FOO") proves that this slot name is inherited from package.
  • Attempting to define the accessor will define it in parent. Thus, polluting the parent package. Or outright breaking compilation if the parent package is locked.

Recipe:

(in-package :cl)
(export 'foofoo)
(defpackage :child (:use :cl))
(in-package :child)
(defclass dummy () ((foofoo :accessor foofoo)))
; => ERROR!

Any idea how we are supposed to deal with this kind of issues in CL?

@aartaka
Copy link
Contributor

aartaka commented Mar 2, 2023

Issue 2 is a fundamental problem with CL and CLOS. Let me describe it:

  • parent package exports foo.
  • In the child package that :uses parent, there is a class with a foo slot. (find-symbol "FOO") proves that this slot name is inherited from package.
  • Attempting to define the accessor will define it in parent. Thus, polluting the parent package. Or outright breaking compilation if the parent package is locked.

Recipe:

(in-package :cl)
(export 'foofoo)
(defpackage :child (:use :cl))
(in-package :child)
(defclass dummy () ((foofoo :accessor foofoo)))
; => ERROR!

Any idea how we are supposed to deal with this kind of issues in CL?

The only idea that comes to my mind is checking the status of the slot symbol for being inherited. But that's yet another heuristic. Not sure that we can do any better, though (´・_・`)

@Ambrevar
Copy link
Member Author

Ambrevar commented Mar 3, 2023

What do you mean? Can you give an example?

@aartaka
Copy link
Contributor

aartaka commented Mar 3, 2023

What do you mean? Can you give an example?

I mean using find-symbol in nclasses (defclass-star.lisp, L280):

(and *automatic-accessors-p*
     (or (and (eq (symbol-package name) *package*)
              ;; NEW CHECK:
              (not (eq :inherited
                       (nth-value 1 (find-symbol name *package*)))))
         (not (eq *accessor-name-package* :slot-name))))

@aartaka
Copy link
Contributor

aartaka commented Mar 3, 2023

Note that the snippet above has a type error, so don't use it verbatim :P

But the idea should be clear, I hope ( ̄(エ) ̄)ゞ

@Ambrevar
Copy link
Member Author

Ambrevar commented Mar 3, 2023

That changes nothing because if the symbol is inherited, then the symbol-package is the parent package, thus the new check is a no-op.

@aartaka
Copy link
Contributor

aartaka commented Mar 6, 2023

Right...

Then, I guess, we have to live with it, debug it, and lock as many packages as we can (҂⌣̀_⌣́)

@Ambrevar
Copy link
Member Author

Ambrevar commented Mar 6, 2023

debug it, and lock as many packages as we can

Debug what? And we are already locking all our packages, what do you mean?

@Ambrevar
Copy link
Member Author

Ambrevar commented Mar 6, 2023

@aadcg Any insights on this?

I have one: in the case of background-buffer in nyxt/web-extensions, we can simply shadow the symbol inherited from nyxt, so that the accessor is defined in nyxt/web-extensions.

Thoughts?

@aadcg
Copy link
Member

aadcg commented Mar 7, 2023

I didn't follow this PR closely as I don't think I can provide much insight... I trust your judgement!

@Ambrevar
Copy link
Member Author

It finally passes! I'll rebase and squash.
Also need to release a new nclasses.

@Ambrevar
Copy link
Member Author

Another issue, although this time it's an easy one: nclasses does not bind slots by default. Shall we explicitly pass nil, or shall we add an option to nclasses to default to NIL?

@aadcg
Copy link
Member

aadcg commented Mar 15, 2023

Shall we explicitly pass nil, or shall we add an option to nclasses to default to NIL?

I can't think of a use case of such an option, so I'd go with the first idea.

@Ambrevar
Copy link
Member Author

I can't think of a use case of such an option, so I'd go with the first idea.

It's standard CLOS: think of it like a symbol that can be boundp or fboundp, sometimes you want to know if it is.

I agree that it is very rarely needed in practice. One use case I can think of is when writing generic code to instantiate object, and you need to tell apart NIL from unbound.

But in this case, nclasses :unbound can be more useful...

Conclusion: I'd go with first option too!

@aadcg
Copy link
Member

aadcg commented Mar 15, 2023

@Ambrevar, thanks for the clarification! It helped me to understand the context of the question :)

@aartaka
Copy link
Contributor

aartaka commented Mar 15, 2023

Let's leave unbound that which shall be unbound.

@Ambrevar
Copy link
Member Author

Let's leave unbound that which shall be unbound.

What do you mean? Example?

@Ambrevar Ambrevar force-pushed the switch-to-nclasses branch 2 times, most recently from 19c7c65 to 8006b4c Compare March 15, 2023 15:22
@Ambrevar
Copy link
Member Author

The sbcl-nclasses Guix package just got updated.

Everything is ready as far as I'm concerned.

I'll merge tomorrow if there is no objection.

@aartaka
Copy link
Contributor

aartaka commented Mar 15, 2023

Let's leave unbound that which shall be unbound.

What do you mean? Example?

I mean we shouldn't default to any value if it's not provided. Yes, :unbound may be used for explicitness, but no value should also mean unbound slot.

Rationale: unbound slot is semantically different from the NIL slot. There are slot-unbould methods that are extremely helpful for cases when slot is intended to be (initially?) unbound. There are lots of libraries (including some of mine) that have ubound slots as the actual part of the API and as something they depend on. So I suggest to leave this useful default as it is, and not infer any default value unless there actually is one.

Think of it as the (values) return: some functions actually need to return no thing. Much like for slots: some have to have no value at all, not even the false/null value. Much like we always return a list from prompter APIs: to delineate cases of no values from the cases of a single NIL value (although I still think we could've utilized multiple-value returns in there). We have to have the distinction between bound and unbound slots.

@aartaka
Copy link
Contributor

aartaka commented Mar 15, 2023

A particular example: I use unbound slots in my cl-telegram-bot-auto-api, because Telegram APIs always return incomplete objects, in which defaulting to NIL means misinterpreting the values into being false, while they are just unbound/unknown. And that's the basis of all this library—leaving unbound that which is truly unbound/unknown.

@Ambrevar
Copy link
Member Author

In practice, I find that NIL is a much more frequent default than :unbound. It's very clear in Nyxt.

Also adopting this default makes for minimal changes in Nyxt itself (this PR is almost exclusively limitied to removing the accessor names).

If you think this is really unreasonable, we could add a class option to specify the slot default.

@aartaka
Copy link
Contributor

aartaka commented Mar 16, 2023

In practice, I find that NIL is a much more frequent default than :unbound. It's very clear in Nyxt.

Yes, but nclasses are also intended as a general audience library and we should be careful to not shoot off any feet :)

If you think this is really unreasonable, we could add a class option to specify the slot default.

That sounds like the best course of action, yes.

@Ambrevar
Copy link
Member Author

OK to merge this and add the nclasses option later?

@aadcg
Copy link
Member

aadcg commented Mar 16, 2023

All good from my side!

@jmercouris
Copy link
Member

yes!

@Ambrevar Ambrevar force-pushed the switch-to-nclasses branch from a2048bb to b266dfd Compare March 17, 2023 08:54
@Ambrevar Ambrevar merged commit 2f39b49 into master Mar 17, 2023
@Ambrevar Ambrevar deleted the switch-to-nclasses branch March 17, 2023 08:55
@Ambrevar
Copy link
Member Author

Done, thank you all!

@aartaka
Copy link
Contributor

aartaka commented Mar 17, 2023

Thanks for working on it <3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Move class-star to a separate repository
4 participants