Introduce fields as part of reify?

301 views
Skip to first unread message

Howard M. Lewis Ship

unread,
Nov 14, 2012, 11:34:30 AM11/14/12
to cloju...@googlegroups.com
I'm in a situation where, if I was using Java code, I would introduce final fields initialized in the constructor. Instead I'm using reify with protocols, and hitting what I perceive to be a small limitation.

Here's a simplified version of what I'm doing.

(reify 
  MyProtocol
  (do-that-thing [this event] (handle-event {:me this} event)))

... in my real code there's a lot of repetition concerning building that context map; in addition, it has to be built all the time, when a single instance could be created once and shared.  

To address this repetition, i'd like to have some syntax for initializing fields of the reified class, perhaps:

(reify
  [context-map {:me this}]

  MyProtocol
  (do-that-thing [this event] (handle-event context-map event)))

In other words, a let-style block of field assignments that are implicitly part of the generated class' constructor, and with values that are in lexical scope of the protocol function implementations.

I think this is a reasonable thing to want to do ... as is, I have several protocol methods that all want to extend a context map with a reference back to this reified instance and I don't like the duplication.

Thoughts?


  

David Powell

unread,
Nov 14, 2012, 11:47:42 AM11/14/12
to cloju...@googlegroups.com
reify closes over surrounding locals, so can't you do:

(let [context-map {:me this}]
(reify
MyProtocol
(do-that-thing [this event] (handle-event context-map event))))

?
> --
> You received this message because you are subscribed to the Google Groups "Clojure Dev" group.
> To view this discussion on the web visit https://groups.google.com/d/msg/clojure-dev/-/oKnRndNNAnQJ.
> To post to this group, send email to cloju...@googlegroups.com.
> To unsubscribe from this group, send email to clojure-dev...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/clojure-dev?hl=en.

Herwig Hochleitner

unread,
Nov 14, 2012, 11:56:00 AM11/14/12
to cloju...@googlegroups.com
+1 to using reify as a closure.

The feature would only be necessary if you need to include "this" in the context-map.

Is it really worth to complect reify in order to gain that possibility? 
Presumably that case could be handled by an extra parameter to handle-event as well, no?


2012/11/14 David Powell <djpo...@djpowell.net>

Howard Lewis Ship

unread,
Nov 14, 2012, 12:23:52 PM11/14/12
to cloju...@googlegroups.com


On Wednesday, 14 November 2012 16:47:44 UTC, David Powell wrote:
reify closes over surrounding locals, so can't you do:

(let [context-map {:me this}]

this has no meaning outside of the reify

Howard Lewis Ship

unread,
Nov 14, 2012, 12:34:47 PM11/14/12
to cloju...@googlegroups.com


On Wednesday, 14 November 2012 16:56:24 UTC, Bendlas wrote:
+1 to using reify as a closure.

The feature would only be necessary if you need to include "this" in the context-map.

Which, ideally, I would. 

In our situation, we are encapsulating request processing with exception handling in the same code: the channel that defines how a request is processed is also responsible for how exceptions are reported back via an external protocol ... some stacks are configured for tcp, others for http, etc.

We pass the context-map to a "stage" in our pipeline, and we really want to have the stage be able to locate this instance of the protocol to invoke the correct pipeline-error function (again, I'm omitting lots of details).

Even if I split things out using a deftype, I just don't see how I can achieve my goal, which is to capture the reified instance in a single map; I'm back to constantly regenerating the map, somewhat needlessly.
 

Is it really worth to complect reify in order to gain that possibility? 

I think "complect" is in the eye of the beholder.

David Nolen

unread,
Nov 14, 2012, 1:11:44 PM11/14/12
to cloju...@googlegroups.com
It still not clear to why the following or something similar doesn't handle your use case.

(defprotocol IContextMap
  (-context-map [this]))

(let [context-map ...]
  (reify
    IContextMap
    (-context-map [_] context-map)
    MyProtocol
    (do-that-thing [this event] (handle-event context-map event))))




  

--

Meikel Brandmeyer

unread,
Nov 14, 2012, 1:32:36 PM11/14/12
to cloju...@googlegroups.com
Hi,

how about delaying the context-map creation?

(let [context-map (promise)
this (reify
MyProtocol
(do-that-thing [this event] (handle-event @context-map event)))]
(deliver context-map {:me this})
this)

Kind regards
Meikel

Christophe Grand

unread,
Nov 14, 2012, 2:05:23 PM11/14/12
to clojure-dev
To me, the devil is in how to introduce this feature without allowing random fns (called from the let-style block) to see an object half initialized?

Anyway, elaborating on Meikel's answer, you may write:

(let [wrapped (promise)
      wrapper (reify MyProtocol
                (do-that-thing [this event]
                  (do-that-thing @wrapped event)))
      context-map {:me wrapper}]
  (deliver wrapped
         (reify MyProtocol
           (do-that-thing [this event]
             (handle-event context-map event))))
  wrapper)

This can be easily macroified and the macrofied code would have no explicit deref.

Christophe








  

--
You received this message because you are subscribed to the Google Groups "Clojure Dev" group.
To view this discussion on the web visit https://groups.google.com/d/msg/clojure-dev/-/oKnRndNNAnQJ.
To post to this group, send email to cloju...@googlegroups.com.
To unsubscribe from this group, send email to clojure-dev...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/clojure-dev?hl=en.



--
Professional: http://cgrand.net/ (fr)
On Clojure: http://clj-me.cgrand.net/ (en)

Herwig Hochleitner

unread,
Nov 14, 2012, 2:05:36 PM11/14/12
to cloju...@googlegroups.com
2012/11/14 Howard Lewis Ship <hls...@gmail.com>

Even if I split things out using a deftype, I just don't see how I can achieve my goal, which is to capture the reified instance in a single map; I'm back to constantly regenerating the map, somewhat needlessly.

I do see your point about having to regenerate the map. 

What this proposal is really about, in my view, is enabling a new class of expressions: Data types, that initialize their fields with expressions using themselves. 
This has no precedence in clojure and has a couple of ramifications.

I think "complect" is in the eye of the beholder.
 

I see three points of complexion:

1) The main issue is the possibility of the instance escaping before it is fully initialized. 
In java, this is the reason you have to call the super constructor before doing anything else but even that rule doesn't fully protect you and you can get very surprising NPEs if you are not aware.
I think this is also the reason we have no such things as constructors in clojure.

2) The primitive in clojure data types is deftype. At least in ClojureScript, reify is defined in terms of deftype. So if we want to allow initializing fields from their instance, we need to take the whole stack into consideration.

3) Circular definition of values only really works in lazy languages like haskell. In clojure, we have letfn allowing a binding to refer to itself. This is safe, because there is guaranteed to be a lambda inbetween.
Let me illustrate: 

In a regular let, this is forbidden, even though there is a lambda inbetween:
(let [val (some-operation (fn [x] {:foo val}))] ...)

This is because some-operation might already call the fn. 

The same problem applies to your reify proposal:
(reify [field (init-expr this)]
  Proto (op [this] (expr-using field)))

What if init-expr calls op?

I'll leave it to your judgement if this complicates matters enough to call it "complected".

Herwig Hochleitner

unread,
Nov 14, 2012, 2:10:20 PM11/14/12
to cloju...@googlegroups.com
Props to cgrand for giving an answer that was was at the same time faster, shorter, more subtle and more complete than mine.

kind regards


2012/11/14 Herwig Hochleitner <hhochl...@gmail.com>

Meikel Brandmeyer

unread,
Nov 14, 2012, 2:28:59 PM11/14/12
to cloju...@googlegroups.com
Hi,

Am 14.11.2012 um 20:05 schrieb Christophe Grand:

> To me, the devil is in how to introduce this feature without allowing random fns (called from the let-style block) to see an object half initialized?

I would put this into its own function.

(defn reify-thingy
[a b & {:keys [context]}]
(let [context-map (promise)
this (reify
MyProtocol
(do-that-thing [this event]
(handle-event @context-map event a b)))]
(deliver context-map (merge context {:me this}))
this))

(let [a (dep-a)
b (dep-b)
r (reify-thingy a b)
x (use-fully-initialised r)]
...)

Meikel


Howard Lewis Ship

unread,
Nov 14, 2012, 3:34:47 PM11/14/12
to cloju...@googlegroups.com, chris...@cgrand.net


On Wednesday, 14 November 2012 19:05:25 UTC, Christophe Grand wrote:
To me, the devil is in how to introduce this feature without allowing random fns (called from the let-style block) to see an object half initialized?

Very true, you would actually always see "this" in a half-realized state, even when building the initial context-map ... so this is probably hopeless.

David Nolen

unread,
Nov 14, 2012, 5:31:48 PM11/14/12
to cloju...@googlegroups.com
Why can't you just implement ILookup and the other relevant bits for map-like behavior if you need it?
--

Howard Lewis Ship

unread,
Nov 15, 2012, 5:56:54 AM11/15/12
to cloju...@googlegroups.com


On Wednesday, 14 November 2012 22:31:49 UTC, David Nolen wrote:
Why can't you just implement ILookup and the other relevant bits for map-like behavior if you need it?

That still doesn't get me closer to having a field of my reified instance containing (a collection containing) the reified instance itself.  

What I have now is more like:

(let [init-map (fn [context] {:me context})]
  (reify 
    MyProtocol
    (do-that-thing [this event] (handle-event (init-map this) event))))

... though in my application's code, the init-map function is a bit more complicated. 

I'd love to be able to avoid invoking init-map for each invocation of do-that-thing, since the result if init-map is always an exactly equivalent map.

I believe I now see what you meant about ILookup; I could implement ILookup and intercept requests for the :me key, and delegate the rest to the other map (in my simplified example, it looks like the context map has only one key, but in the real app, there's a context map for the instance with a number of key/value pairs already in it, to which I want to add a new key/value pair to identify the reified instance).  However, the code inside handle-event needs that parameter to be a map to which new keys can be assoc-ed.


On Wednesday, November 14, 2012, Howard M. Lewis Ship wrote:
I'm in a situation where, if I was using Java code, I would introduce final fields initialized in the constructor. Instead I'm using reify with protocols, and hitting what I perceive to be a small limitation.

Here's a simplified version of what I'm doing.

(reify 
  MyProtocol
  (do-that-thing [this event] (handle-event {:me this} event)))

... in my real code there's a lot of repetition concerning building that context map; in addition, it has to be built all the time, when a single instance could be created once and shared.  

To address this repetition, i'd like to have some syntax for initializing fields of the reified class, perhaps:

(reify
  [context-map {:me this}]

  MyProtocol
  (do-that-thing [this event] (handle-event context-map event)))

In other words, a let-style block of field assignments that are implicitly part of the generated class' constructor, and with values that are in lexical scope of the protocol function implementations.

I think this is a reasonable thing to want to do ... as is, I have several protocol methods that all want to extend a context map with a reference back to this reified instance and I don't like the duplication.

Thoughts?


  

--
You received this message because you are subscribed to the Google Groups "Clojure Dev" group.
To view this discussion on the web visit https://groups.google.com/d/msg/clojure-dev/-/oKnRndNNAnQJ.
To post to this group, send email to cloju...@googlegroups.com.
To unsubscribe from this group, send email to clojure-dev+unsubscribe@googlegroups.com.

Christophe Grand

unread,
Nov 15, 2012, 8:40:54 AM11/15/12
to Howard Lewis Ship, clojure-dev
See https://gist.github.com/4078651 for the reify-let macro which throws an exception when one tries to call a method on the falf-initialized object.

Christophe

David Nolen

unread,
Nov 15, 2012, 9:29:00 AM11/15/12
to cloju...@googlegroups.com
(let [ctor (fn ctor [ext-map]
               (reify
                  ILookup
                  (valueAt [_ k not-found]
                    case k
                    :me this
                    (get ext-map not-found))
                  IAssociative
                  (assoc [_ k v]
                    (ctor (assoc ext-map k v))
                  ...))]
  ...)

You can easily support assoc'ing new keys.


To view this discussion on the web visit https://groups.google.com/d/msg/clojure-dev/-/o26H0xsrpbEJ.

To post to this group, send email to cloju...@googlegroups.com.
To unsubscribe from this group, send email to clojure-dev...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages