guile-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Mutating public bindings of a declarative module


From: Amirouche Boubekki
Subject: Re: Mutating public bindings of a declarative module
Date: Mon, 25 Nov 2019 07:23:37 +0100

Le dim. 24 nov. 2019 à 18:54, Ludovic Courtès <address@hidden> a écrit :
>
> Hello!
>
> It seems that if you ‘set!’ a public variable of a declarative module,
> the change is visible to all the module users, but it’s not necessarily
> visible to procedures within that module, presumably because they use an
> inlined or specialized variant of that thing.
>
> I would have imagined that public bindings are considered mutable and
> thus not subject to inlining; OTOH, that would obviously be a loss, so
> the current approach makes sense.
>
> Anyway, it complicates a use case for me.  In Guix, we “mock” bindings
> like so:
>
>   (define-syntax-rule (mock (module proc replacement) body ...)
>     "Within BODY, replace the definition of PROC from MODULE with the 
> definition
>   given by REPLACEMENT."
>     (let* ((m (resolve-interface 'module))
>            (original (module-ref m 'proc)))
>       (dynamic-wind
>         (lambda () (module-set! m 'proc replacement))
>         (lambda () body ...)
>         (lambda () (module-set! m 'proc original)))))
>
> and that allows us to write tests that temporarily modify public (or
> private!) bindings.
>
> It seems like this could be addressed by compiling selected modules with
> ‘user-modules-declarative?’ set to #false, or by avoiding the above hack
> altogether when possible, but I thought I’d share my impressions and
> listen to what people think.  :-)
>

For what it is worth, in my project I take a different approach to
mock. Some may call it inversion-of-control of something like that.
Basically, everything that must be mocked is passed as a procedure.

For instance, in babelia there is a pool of thread worker with a
fibers mainthread. There is three primitives: initialize the thread
pool, apply a thunk in a worker, and for-each-par-map. During the
tests I can not run the pool of thread worker because of the #:drain
behavior [0], I could workaround it some other way like explained in
the ticket by Wingo.

[0] https://github.com/wingo/fibers/issues/30

Anyway, the other advantage of making the thread pool configurable is
that my OKVS abstraction is not tied to it. The full-text search
abstraction dubbed fts is passed `apply` and `for-each-map` as last
two arguments in the constructor:


  (define-record-type <fts>
    (make-fts engine ustore prefix limit apply for-each-map)
    fts?
    (engine fts-engine)
    (prefix fts-prefix)
    (ustore fts-ustore)
    (limit fts-limit)
    (apply %fts-apply)
    (for-each-map %fts-for-each-map))

Then during the tests or else, I can pass custom implementations:

  (define (for-each-map sproc pproc lst)
    (for-each sproc (map pproc lst)))

  (define fts (make-fts engine
                        ustore
                        '(test-fts-prefix)
                        1
                        (lambda (thunk) (apply thunk '())) ;; fts-apply
                        for-each-map))

Similarly, in OKVS SRFI, see [1], to make database engine swappable, I
rely on a similar pattern that was dubbed typeclass object by SRFI-128
(comparators). In those cases, the record instance contains only
procedures.

[1] 
https://github.com/scheme-requests-for-implementation/srfi-167/blob/master/srfi/engine.sld#L1

The approach I described, that boils down to passing a wanna be mocked
procedure as argument, can work.

> Thanks,
> Ludo’.

Hope this helps.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]