-
-
Notifications
You must be signed in to change notification settings - Fork 426
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
General-purpose hooks and functional config #483
Conversation
Left to do:
|
@ruricolist In ruricolist/serapeum#41 (comment) you mentioned a trick to check the arity of a function. I'm not sure I understand how that would work in practice, can you explain? |
The idea I had was that when you add the function to the hook, you get the arity of the hook and then (at runtime) compile a lambda that calls the function with that arity. Here's what it looks like with SBCL: (defun x (y z) (list y z))
(compile nil '(lambda (a b c) (x a b c)))
; caught WARNING:
; The function X is called with three arguments, but wants exactly two.
(let ((fn (lambda (y z) (list y z))))
(compile nil `(lambda (a b c) (funcall ,fn a b c))))
; caught WARNING:
; The function (LAMBDA (Y Z)) is called with three arguments, but wants exactly two. Clozure signals a warning for the global definition, but unfortunately not for the local definition. |
What about just using `declare` then? SBCL seems to do the right thing,
+ it allows for catching type errors for the return value as well:
```lisp
(let ((fn (lambda (y z) (list y z))))
(declare (type (function (integer integer) string) fn))
fn)
; in: LET ((FN (LAMBDA (Y Z) (LIST Y Z))))
; (LET ((NEXT::FN (LAMBDA (NEXT::Y NEXT::Z) (LIST NEXT::Y NEXT::Z))))
; (DECLARE (TYPE (FUNCTION (INTEGER INTEGER) STRING) NEXT::FN))
; NEXT::FN)
;
; caught WARNING:
; Derived type of (LAMBDA (Y Z)) is
; (FUNCTION (T T) (VALUES CONS &OPTIONAL)),
; conflicting with its asserted type
; (FUNCTION (INTEGER INTEGER) (VALUES STRING &REST T)).
; See also:
; The SBCL Manual, Node Handling of Types
;
; compilation unit finished
; caught 1 WARNING condition
#<FUNCTION (LAMBDA (Y Z)) {539C099B}>
```
|
Actually I have a problem with `declare`: how do we get the type
programmatically from the hook?
It seems that we may only be able to do this if `add-hook` were a macro.
Thoughts?
|
It occurred to me that we could use a macro called set-default or something which expands into a hook that runs after initialization, I’m much more of the opinion that compile time checking and slots is not important, the slot set function can do the checking. This is much more towards proper encapsulation of OO data structure anyways. I know this is not strictly related to only the contents of this pull request, but I can’t think of anywhere else it belongs. |
I think it belongs to #419 where I've offered a non-hook solution ;)
|
Great news! I've figured a way to use macros to generate typed hooks and typed handlers, and now The only drawback: The types must be declared before use. In practice, it looks like this: (define-hook-type string->string (function (string) string)) ; Need to be done only once for this type.
;; Then
(defvar *my-hook* (make-instance 'hook-string->string))
(defun my-settings1 ()
(add-hook *foo* (make-handler-string->string (lambda (s) (str:concat "foo-" s)) :name 'foo)
(defun my-settings2 ()
(add-hook *foo* (make-handler-string->string (lambda (s) 17) :name 'foo)))
; caught WARNING:
; Derived type of (LAMBDA (S) :IN MY-SETTINGS2) is
; (FUNCTION (T) (VALUES (INTEGER 17 17) &OPTIONAL)),
; conflicting with its asserted type
; (FUNCTION (STRING) (VALUES STRING &REST T)). What do you think? @ruricolist ? |
In particular, the above approach may supersede @ruricolist arity-checking technique since it non only checks the arity but also the argument and return value types. |
This is copy-pasted from serapeum's hook implementation.
Not sure it's a good idea though.
It works but it does not compose well to add new types.
We need to decide with Serapeum which one to keep.
Is there anything yet blocking us from merging this in? |
Yes, everything mentioned at the top:
- Replace cl-hooks with this.
- Document.
- Add tests.
I'm on it.
|
I think you mixed up the terms here. The Note that handlers cannot be duplicated with my typed-hook implementation. My question was about listing hooks, not handlers.
Actually there is checking that could be done:
Keeping track of the handler has at least 2 use cases:
The user could very well keep track of the handlers themselves, but then all
It's Common Lisp, un-exported symbols can always be accessed with The question was whether we tell the user "this is OK to use" or not.
Some options:
When run, a hook may return a value. Besides, for some combination we want to adopt a specific behaviour. Does that make more sense? |
You are correct, I had mixed up the terms in my head for that paragraph for some reason. I still think a function to list hooks would be useful as part of the discoverability and interactivity of the platform. I imagine we could bind a command like
How so? Could I not add two lambda handlers that do the same thing?
I think we should allow people to shoot themselves in the foot. I have consistently been against adding in more checking of user valid input. The reason is:
It is too soon to do either of those things. Checking types, at this point, for most users, simply does not add as much value as a new feature. In other words, for the time being, I believe our efforts are better spent elsewhere.
I guess I don't see the purpose here exactly, but if you think it would be useful instead of having every user keep a reference to whatever they are testing, why not!
Ah, yes, export the symbol.
I should think that Serapeum should change, @ruricolist ?
That makes sense now, I believe. I however, do not have any ideas... I will think about it :-) |
Yes listing the hooks would be useful, but the question is how do we list them?
See the docstrings, the tests and this thread. Lambdas are only accepted with
I think you are talking about a different thing, which is a different problem.
I'm leaning towards not exporting it. The main intent of this library is to expose |
Yes, I saw, but I mean two lambdas, two names, do the same thing. I don't believe this can be prevented :-D
I'm not talking about a different thing. I am referencing a different thing, but is the same idea here. I don't believe constructors with extra checks are necessary.
Well...as you wish :-D I don't have a very strong opinion on this. |
I've changed the signature of serapeum:run-hooks to allow arguments. Is there anything else you need from me for this? |
@ruricolist: I think you meant |
An empty hook is a valid hook.
It is, but the question is about handlers: do we need to add a |
Hooks don't have names. Did you mean something else?
Indeed, this is what I had in mind: generate constructors in the |
I mixed up with handlers and make-handler. |
I believe this is ready to merge. I'll merge tomorrow if there is no objection. |
No objection, please feel free to merge! |
May I merge this in on your behalf? |
I'll do it as soon as possible, no worries!
|
OK, thank you! I was just asking because I want to do quite a few file operations (renaming base.lisp to start.lisp etc) in preparation for our new work and I want the merges to be nice and easy! |
Merged. |
Work in progress, not done yet.
So I've addressed many of the points mentioned in #419.
Those hooks are based off Serapeum's hooks. They fix most (all?) issues in #383.
The biggest issue I'm facing at the moment is type-checking: I'd like to check that the handlers match the type specified in the the
hook
object, but it seems impossible to do in Common Lisp:https://old.reddit.com/r/Common_Lisp/comments/ds4tpy/programmable_and_compiletime_function_typechecking/?
This is a general issue: how do we validate the type of a variable / slot that's supposed to host a function?
I've implemented some form of global, ahead-of-time hooks that can be bound to any symbol / object. It's a very naive implementation, I don't know if that would suit everyone's needs.
This implementation makes some parts of Serapeum's hooks irrelevant, such as
run-hook-with-args
and derivatives.@ruricolist @scymtym What do you think?