[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.