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
Class star #889
Class star #889
Conversation
To do: Move |
More to do: Add helpers to retrieve original class. |
I've implemented all the missing features. I still need to do some more testing, then I'll merge. @jmercouris: What do you think of my zero-value-inference customization? See the readme. |
This is one of the first times I have just been absolutely speechless! I cannot stress enough how awesome this is. This is just fantastic! I really like how we can utilize these objects so seamlessly for configuration of the program. After all, all of our code surface is API, so this really helps in deciding how we expose it, and automating that. As per the zero-value inference- brilliant. Why isn't this done elsewhere? It seems so obvious now! Of course, not everything is sunshine and rainbows. You forgot the spdx license header :-P |
One question: wouldn't it be better to do the other way around,
i.e. infer the type from the initform?
`type-of` is not idea here because `(type-of "")` returns `(SIMPLE-ARRAY
CHARACTER (0))` for instance.
So we could have a function like our basic-type-zero-values here that
maps the return value of `type-of` to more useful types.
Thoughts?
|
Example:
(cond
((subtypep (type-of ARG) 'string)
'string)
...)
|
Implemented.
I'm now using type inference in define-class for BUFFER.
I'll update BROWSER, WINDOW and MINIBUFFER.
I'll soon merge on master unless you want to keep this on hold.
|
Please, merge as soon as ready! |
@@ -0,0 +1,220 @@ | |||
(in-package :class*) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
license header missing
@@ -0,0 +1,17 @@ | |||
(in-package :cl-user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
license header missing
@@ -0,0 +1,138 @@ | |||
(in-package :cl-user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
header
@@ -882,10 +882,10 @@ As a second value, return the current buffer index starting from 0." | |||
(webkit-history-entry-gtk-object history-entry))) | |||
|
|||
(defun set-renderer () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about this, why is this a function instead of all of the invokations being at the top level? The idea being that you can set the renderer at runtime to something else?
@aartaka: If I merge now it will create a few conflict with your current
patch, is that OK?
|
Initially I was using this trick for CCL.
More generally, 2 reasons:
- It groups all the impure, global settings in one spot.
- I was planning to call (set-renderer) from (start) to avoid
side-effects when loading the system. I haven't done it because it
turned out that CCL worked without this trick. I still believe it's
good practice to avoid ASDF-time side effects, so we should do it as
some point.
|
OK, that makes some sense. Perhaps we'll drop it into start at some point then :-) |
885fab2
to
b769fe7
Compare
Latest measures: class-star just allowed us to remove some 400 lines of
code from the code base!
This is not even counting the word count reduction.
|
396ae64
to
1b5ebc3
Compare
It seems to work alright so far, except for the last commit which
makes the minibuffer blank, I don't know why.
Could anyone test?
@jmercouris?
|
I can test later today. |
When trying to show the buffer list. This is because of the fdeclaration:
|
I can confirm that the minibuffer is blank. I have no idea why that could be. It does function as expected... |
I've been boggling my mind around this issue for a while. It seems that Then I remembered a paradigm I once read: composition is often better Structure/Class composition is often done with mixins or equivalents. I suggestion the following paradigm:
In practice, we would have the following classes which don't inherit
And so on. Then we would define the classes:
All calls to Then in
We override the user-classes. Note that it does not modify the If the user wants to change, say, the list of default modes for both web (defclass my-buffer ()
((default-modes ...)))
(defclass user-web-buffer (my-buffer web-buffer gtk-buffer buffer) ())
(defclass user-internal-buffer (my-buffer internal-buffer gtk-buffer buffer) ()) The (define-configuration (user-web-buffer user-internal-buffer)
((default-modes ...))) which would roughly expand to the above, by adding the generated class If we want to customize a class multiple times, no problem: (defclass my-buffer2 ()
((default-url...)))
(defclass user-web-buffer (my-buffer2 my-buffer web-buffer gtk-buffer buffer) () Since (define-configuration (user-web-buffer user-internal-buffer)
((default-modes ...)))
(define-configuration (user-web-buffer user-internal-buffer)
((default-url ...))) @jmercouris Let me know if this approach seems sound to you and I'll EDIT: Added details about the user-CLASSES definitions in |
Great news! I've just updated the patch set to use class composition
instead of inheritance, and it fixes everything!
It also makes lots of stuff much, much simple.
As you can see in the last commit, the changes were quite minimal.
The main question that remains is "how shall we name the 'final'
classes?"
In the following:
- I call "abstract" the class that contains the whole
definition (all the slots);
- I call "final" the class the only inherits from the abstract class +
eventual specialization classes.
Two strategies:
1 .For now I've prefixed the final class with "REPLACEME-". We need to
find a better term of course ;)
The benefit is that it induces very few changes (e.g. all the `buffer`
occurrences need not change). The drawback is that the user must be
aware that the instantiated object are all prefixed with REPLACEME-.
2. But we could also do the other way around and prefix the abstract
classes (say with ABSTRACT-) and call the final classes by their
canonical name. This would be prettier for the user, but it induces a
bigger refactoring.
|
…n and fix mode config.
I need some more time to think about this. Thank you for a very deep investigation. I think that we are close to a good solution. |
My first thought is that we can get a large quantity of simplification by reducing the requirements. For example, updating inheritees automatically may not be necessary. |
Another benefit of this strategy is that it's now possible to _remove_
classes from the composition.
Before, if we had REPLACEME-buffer inherit from user-buffer1, which inherited
from buffer, it was impossible to remove the customization induced by
user-buffer1.
Now we can remove user-buffer1 with something like this (untested):
```lisp
(defmacro define-final-class (name &rest superclasses)
`(defclass ,(intern (str:concat "REPLACEME-" (string name)) (,superclasses))))
(define-final-class buffer (delete 'user-buffer1 (mopu:direct-superclasses 'REPLACEME-buffer)))
```
|
It may not, but it would trip the user many times for sure.
A good example is the default-modes BUFFER slot. Enabling emacs-mode in
BUFFER did mirror the change in INTERNAL-BUFFER.
That said, it's OK, because it works now!
|
Calling the destructive function Use |
@phoe: Thanks, both good points!
Left to do:
- Remove superclass cycle detection.
- Rename REPLACEME- to user-.
|
This allowed us to override a class by itself, e.g. (define-class foo (foo) ((new-slot ...))) This is brittle however: typing is not well specified then on the various implementations. The children of FOO might not be of the new FOO type. This may also break method dispatch. Overall, it's a bad idea.
Done!
This is ready to merge.
|
libraries/class-star/class-star.lisp
Outdated
((subtypep type 'hash-table) (make-hash-table)) | ||
;; Order matters for numbers: | ||
((subtypep type 'integer) 0) | ||
((subtypep type 'complex) #c(0 0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This decays into the integer 0
. (Try it in the REPL!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! I've changed the logic to partition between floats and the rest of the number types
libraries/class-star/class-star.lisp
Outdated
;; Order matters for numbers: | ||
((subtypep type 'integer) 0) | ||
((subtypep type 'complex) #c(0 0)) | ||
((subtypep type 'number) 0.0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you support rationals? Perhaps you might want to form an exhaustive partition of reals into float
s and rational
s?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this is a zero-value inference, not a type inference. (Only exceptionally useful.)
ratio
is a subtype of number
, so the zero value is handled here (see the latest commits which partitions between floats and the rest).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rational zero is 0
, not 0.0
, which is why I've actually made this comment. Sorry for being unclear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, you were right, this didn't do the right thing for rationals. The latest commit should fix it.
libraries/class-star/class-star.lisp
Outdated
(handler-case (basic-type-zero-values type) | ||
(error () | ||
;; Compile-time error: | ||
(error "Missing initform."))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this here? The capture-and-resignal behavior seems silly, especially since 1) you explicitly discard information coming from the original error, 2) you destroy the stack coming from the original error by using handler-case
instead of handler-bind
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This trick is was not meant to expose conditions to the user, only to save me some 10 lines of code :/
It's not very useful and arguably confusing so I've replaced it with multiple return values (consistent with get-properties
).
source/auto-mode.lisp
Outdated
(let ((*package* *package*)) | ||
(in-package :nyxt/auto-mode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is equivalent to (let ((*package* (find-package :nyxt/auto-mode))) ...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Fixed.
It's okay, especially given that it will save lots of lines in the code of my patch at the cost of minor fixes :) |
Complex #c(0 0) is decayed to 0, so it's the same as any other number except for floats.
…ltiple values. This is less confusing and more functional.
I've done some testing, it looks good to me. I like this solution. It is a good place that we can build on top of! |
Unfinished, do not merge!
This is to show-case the new
define-class
macro.I decided to base it on top of hu.dwim.defclass-star because it saved me hours of work and I'm a bit short on time. If we ever need to replace defclass-star in the future, it won't be a problem.
hu.dwim.defclass-star has only 1 dependency, it's a rather light addition and it's already packaged for Guix.
Features
All the defclass-star goodies, including global/local class/slot exports, automatic initargs and automatic accessors.
Inferred initforms. "Do what I mean" by default (i.e. use the zero value for simple types, nil otherwise). The initform can always be specified manually. I'll make the inference fully customizable later.
Cycle detection in superclasses: We can now extend classes like this
Result
I've applied the change to the window class. As a result, we save more than 20 annoying lines. For bigger classes, the gain is even bigger.
No more redundancies which means no more risks for typos.
What's left to be done
The tests are not finished and I need to polish the zero-value inference.
Feedback welcome!