Error formatting macro: pagetree: java.lang.NullPointerException

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Problem statement

The actual prototype of js objects is not settable. Thus it's not possible to assign a protocol implementation a live objects, e.g. input from jsonp.

The fields needed to let a javascript object participate in a protocol could simply be assigned, however the algorithm to get the name-mangled protocol fields is woven into extend-type, tighly coupled to target the .protocol property.

Name, Interface?

The operation to extend an instance will be called specify in the next proposal and have an interface similar to deftype. See discussion at CLJS-398

Proposal: CLJS-414

Outline of the implementation

extend-type now expands to specify on the .prototype property.

specify expands to specify* of protocols and method-arity-maps with fn expressions.

Kernel operation: specify*

specify* is loosely based on clojure.core/extend, in that it takes maps of implementing closures.

Justification

specify* is needed, because currently CLJS doesn't lift lambdas, so new function objects are allocated every time specify is evaluated. This is fine for specifying prototypes or configuration-object-like protocol instances. It is not so fine for specifying 1000 objects to take part in IEquiv and IHash.

Differences from extend

  • It is a macro
    • bootstrapping: takes a PersistentMap parameter
    • gclosure minification + no reflection layer: clojurescript doesn't know how to access name mangled protocol fields
  • It takes method names as symbols
    • by the time we have an optional reflection layer there might be a cljs.reflect/specify with same interface as extend
  • Takes arities in addition to protocol and method name: (specify* o P {-m {1 m-impl-1, 3 m-impl-3}})
    • OK for a low-level user visible operation??
    • we allow users to implement only parts of a protocol anyway
    • use single-arity fast path
    • no need to unpack single-arity impls from multi-arity fns, which breaks down if user passes javascript fn
    • abstracts protocol name mangling: essentially a setter generator

extend-type refactored: specify

  • Takes fn bodies like extend-type and instantiates them
  • extend-type now just expands to a specify on the .prototype property
  • 'Object' pseudo protocol lives here

Example

Code Block
(deftype A
  ILookup
  (-lookup ([o k] (aget s k))
           ([o k nf] (if (.hasOwnProperty o k) (aget o k) nf))) 
;; expands to

(specify (.-prototype A)
  ILookup
  ...)

;; expands to

(specify* (.-prototype A)
  ILookup {-lookup {2 (fn [o k] ..)
                    3 (fn [o k nf] ..)}})

;; and if we want to specify a whole batch
;; we can have the same method instances attached to every object
  
(let [m2 (fn [o k] ..)
      m3 (fn [o k nf] ..)] 
  (defn specify-alookup! [o]
    (specify* o
      ILookup {-lookup {2 m2 3 m3}})))

(defn jsonp-callback [os]
  (process (map specify-alookup! os))) 

 

Issues

  • specify* can't be used with IFn in the current patchset to CLJS-414 as of writing
    • just realized that this is a bug, will strike out when fixed
  • specify expects to see each method only once, should error when user tries to do
Code Block
(extend-type T
 P
 (meth [a] "one")
 (meth [a b] "two") )
  • Is specify* too low level?
    • Are users that just want the methods reused better served by defspecifyer with deftype syntax?
    • Might just be a temporary solution until we get lambda lifting anyway
    • Are there other use cases?