• Local/temporary methods in CLOS

    From Stefan Monnier@21:1/5 to All on Tue Mar 11 17:01:19 2025
    I need some way to define "local" methods, which can override
    temporarily "global" methods and obey some kind of dynamic scoping rule.

    Assuming I'm not the first to feel such a need, what are usual
    approaches to try and provide that kind of behavior?


    Stefan

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Monnier@21:1/5 to All on Tue Mar 11 18:02:05 2025
    I need some way to define "local" methods, which can override
    temporarily "global" methods and obey some kind of dynamic scoping rule.

    I feel that your requirements may be somewhere in the locus of
    Pastal Costanza's work, like ContextL.

    https://github.com/pcostanza/contextl

    Also see Costanza's paper "Dynamically Scoped Functions as the
    Essence of AOP" [2003].

    Thanks! That's a nice solution, and the underlying implementation
    technique makes a lot of sense (it refines the manual solution I've
    been playing with).

    Assuming I'm not the first to feel such a need, what are usual
    approaches to try and provide that kind of behavior?

    Since in Common Lisp we don't have dynamically scoped functions, only variables, the straightforward thing, without using anyone's framework,
    would be to use special variables to hold certain functions of interest,
    and funcall them. (That can be wrapped or macroed over.)

    (defun overridable-fun (&rest args)
    (apply *overridable-fun* args))

    (let ((*overridable-fun* (lambda (...) ...)))
    (overridable-fun 42))

    For dynamically-scoped local functions, that's indeed what I typically
    use (or alternatively, I simply pass that function explicitly down the
    stack), but my question is for the case where I want to do it for
    methods, i.e. for "part" of a function (I guess I could simulate it by overriding with a function that tests the args' type and delegates to the
    old function, but then I get a kind of "linear-time" dispatch if there
    are N local methods, which I'd rather avoid).


    Stefan

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Stefan Monnier on Tue Mar 11 21:41:02 2025
    On 2025-03-11, Stefan Monnier <monnier@iro.umontreal.ca> wrote:
    I need some way to define "local" methods, which can override
    temporarily "global" methods and obey some kind of dynamic scoping rule.

    I feel that your requirements may be somewhere in the locus of
    Pastal Costanza's work, like ContextL.

    https://github.com/pcostanza/contextl

    Also see Costanza's paper "Dynamically Scoped Functions as the
    Essence of AOP" [2003].

    Assuming I'm not the first to feel such a need, what are usual
    approaches to try and provide that kind of behavior?

    Since in Common Lisp we don't have dynamically scoped functions, only variables, the straightforward thing, without using anyone's framework,
    would be to use special variables to hold certain functions of interest,
    and funcall them. (That can be wrapped or macroed over.)

    (defun overridable-fun (&rest args)
    (apply *overridable-fun* args))

    (let ((*overridable-fun* (lambda (...) ...)))
    (overridable-fun 42))

    We can think about macros to make this look nicer.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Stefan Monnier on Wed Mar 12 00:08:46 2025
    On 2025-03-11, Stefan Monnier <monnier@iro.umontreal.ca> wrote:
    I need some way to define "local" methods, which can override
    temporarily "global" methods and obey some kind of dynamic scoping rule.

    I feel that your requirements may be somewhere in the locus of
    Pastal Costanza's work, like ContextL.

    https://github.com/pcostanza/contextl

    Also see Costanza's paper "Dynamically Scoped Functions as the
    Essence of AOP" [2003].

    Thanks! That's a nice solution, and the underlying implementation
    technique makes a lot of sense (it refines the manual solution I've
    been playing with).

    Assuming I'm not the first to feel such a need, what are usual
    approaches to try and provide that kind of behavior?

    Since in Common Lisp we don't have dynamically scoped functions, only
    variables, the straightforward thing, without using anyone's framework,
    would be to use special variables to hold certain functions of interest,
    and funcall them. (That can be wrapped or macroed over.)

    (defun overridable-fun (&rest args)
    (apply *overridable-fun* args))

    (let ((*overridable-fun* (lambda (...) ...)))
    (overridable-fun 42))

    For dynamically-scoped local functions, that's indeed what I typically
    use (or alternatively, I simply pass that function explicitly down the stack), but my question is for the case where I want to do it for
    methods, i.e. for "part" of a function (I guess I could simulate it by

    OK, so this is something else: dynamically specializing a generic
    function with methods which then go away at the end of the scope.

    The obvious, but perhaps cumbersome, way which occurs to me for doing
    this is to swap the entire generic function for another one
    with the mechanisms we have discussed above.

    The replacement generic function can have the needed methods.
    (But would it still need to? We have all the indirection we need
    already.)

    I suspect Costanza's ContextL may speaks to these requirements.

    You are in some scope in which you would like to modify the behavior
    of a generic function, so it has different methods. In "COP"
    (context-oriented programming) that behavior would belong to a layer.
    You dynamically activate and deactivate the layer (by name, I think).
    The layer defines the methods (so physically they are not locally
    enclosed in the scope(s) where you are activating the layer). That's a
    feature, since you can activate a layer in multiple places.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Monnier@21:1/5 to All on Tue Mar 11 23:00:07 2025
    I suspect Costanza's ContextL may speaks to these requirements.

    It does.

    You are in some scope in which you would like to modify the behavior
    of a generic function, so it has different methods. In "COP" (context-oriented programming) that behavior would belong to a layer.
    You dynamically activate and deactivate the layer (by name, I think).
    The layer defines the methods (so physically they are not locally
    enclosed in the scope(s) where you are activating the layer). That's a feature, since you can activate a layer in multiple places.

    Here's an example:

    Say I implement a small DSL as a macro. That DSL may itself want to
    offer the ability to extend the language with macros. Each macro and
    special form in the DSL could map to a method (using an `eql`
    specializer on the head of the term, typically), so if you want your DSL
    to offer local macros (think `macrolet`), you need to temporarily add
    methods during the macro expansion of the subterms. For some DSLs it
    might make sense to define sets of macros which can be thought of as "namespaces", and then locally activate them, which maps neatly to
    ContextL's notion of layers and temporary activation of them.

    To take DSL examples from ELisp:

    - PEG has `define-peg-rule` (which could be implemented as defining
    a new method) but it also has `define-peg-ruleset` (which could map
    to defining a new layer) and then `with-peg-rules` (which can
    locally activate layers, and also define a new layer "on the spot").
    - RX has `rx-define` as well as `rx-let` (which both defines a layer
    and activates it) but no way to define a layer separately from
    its activation. Yet, looking at RX users, the ability to define a set
    of RX constructs and activate them later would be welcome.
    - Pcase has `pcase-defmacro` but not "macrolet" equivalent. It would
    make a lot of sense to add some way to have local Pcase macros.


    Stefan

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)