nedap/utils.modular

Dynamic protocols

Opened this issue · 3 comments

vemv commented

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

vemv commented

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?

vemv commented

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.