Dynamic protocols
Opened this issue · 3 comments
I think a variant of defprotocol that replaced the 'receiver' argument (this
) with an auto-generated dynamic variable would yield much more pleasant codebases to deal with, reducing the friction of using protocols and Component.
(dynamic/defprotocol Foo
(do-it [x y]) ;; will emit something like `(--do-it [this x y])`
;; will also emit *this* var, shared across all protocol methods
;; this assumes only one protocol exists per ns, which is the recommended pattern anyway (soon to be enforced)
)
(defn do-it-impl [x y] ;; a protocol method implementation. No receiver argument necessary
(println (+ x y))
(println *this*))
(implement {}
--do-it do-it-impl)
;; In consumer code: ----------
;; this is where the proposed mechanism shines.
;; I don't have to find (and pass around) a component implementing a protocol:
;; Component will bind *this* to the adequate component.
(do-it x y)
;; So, programmers can have the illusion of using plain functions instead of components or protocols at all,
;; reducing the perceived indirection of having clean/testable code.
dynamic/defprotocol
should be built on top of speced/defprotocol
.
Just an idea for now, might be unfeasible
I got a better idea: creating protocols out of defns, with a default implementation. Greatly reduces boilerplate.
Sample:
;; Does a few things:
;; Emits a *this* variable
;; Emits a defprotocol, with --method-1 and --method-2 methods
;; `declare`s method-1, method-2, to be actually implemented by the programmer below
;; creates a default metadata-based implementation, backed by `method-1 and `method-2
;; sets the default implementation as the default value bound to *this*
(emit-protocol
method-1
method-2)
(defn method-1 [x y]
(-> *this* :foo println))
(defn method-2 [x y z]
(* x y z))
Caveat: consumers would end up loading impl code even if they only wanted the protocol. This is ok 90% of the times. When not, things can be refactored into separated namespaces.
I looked at this issue before, but hadn't gasped the emit-protocol part fully. Took another look and it seems actually very nice. Although I'm wary for editor integration. Wdyt?
I guess the *this*
part will be inherently problematic in Cursive (whereas an explicit this
argument can be resolved)
Also, the idea outlined in #4 (comment) might be unfeasible due to the seeming circular dep.
Something like this could solve it:
;; emits a protocol and a set of functions implementing that protocol
(defclass Thing [field-x field-y]
(bark [this]
(println [*this* field-x field-y]))
(swim [_]
(println "swimming...")))
I have implemented something very similar in the past.