Allow Data Structure to Be Called as Function

231 views
Skip to first unread message

RJ Nowling

unread,
Jun 15, 2011, 3:53:39 PM6/15/11
to Clojure
Hi,

I'm sorry if this has been asked before, but I would like to know how
to create data structures in Clojure that can be used in the same way
as the built-in data structures. For example, I can access the
elements of a vector by (my-vec 1). How can I implement this
interface when creating a data structure in Clojure?

Thanks,
RJ

Ken Wesson

unread,
Jun 15, 2011, 3:57:53 PM6/15/11
to clo...@googlegroups.com

(defrecord Foo [...]
...
IFn
(invoke [this] (do-this-on-zero-argument-call))
(invoke [this x] (do-when-called-with-x))
(invoke [this x y] (+ x y)))

=> ((Foo.) 33 9)
42
=>

--
Protege: What is this seething mass of parentheses?!
Master: Your father's Lisp REPL. This is the language of a true
hacker. Not as clumsy or random as C++; a language for a more
civilized age.

Michael Nygard

unread,
Jun 15, 2011, 6:59:20 PM6/15/11
to clo...@googlegroups.com
You could also use reify:

(defn make-foo [s] 
  (reify clojure.lang.IFn 
    (invoke [this] (str "Hello, " s))))

((make-foo "RJ"))
"Hello, RJ"

I have to admit, though, that I'm unclear on the relative merits of defrecord vs. reify. Anyone want to comment?

Cheers,
-Michael Nygard

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Michael T. Nygard

Release It! Design and Deploy Production-Ready Software

Beautiful Architecture

97 Things Every Software Architect Should Know

RJ Nowling

unread,
Jun 15, 2011, 10:45:33 PM6/15/11
to Clojure
Thank you, Ken and Michael. Not knowing how to do that bothered me
since it felt like I couldn't create data structures that were on the
same level of the built-in data structures. :)
> mtnyg...@gmail.comhttp://www.michaelnygard.com/
>
> Release It! Design and Deploy Production-Ready Softwarehttp://bit.ly/ReleaseIt
>
> Beautiful Architecturehttp://bit.ly/BeautifulArchitecture

Stuart Halloway

unread,
Jun 16, 2011, 2:54:20 AM6/16/11
to clo...@googlegroups.com
You could also use reify:

(defn make-foo [s] 
  (reify clojure.lang.IFn 
    (invoke [this] (str "Hello, " s))))

((make-foo "RJ"))
"Hello, RJ"

I have to admit, though, that I'm unclear on the relative merits of defrecord vs. reify. Anyone want to comment?

Cheers,
-Michael Nygard

defrecord gives you a lot of stuff, even before you start to add interfaces / protocols:

(defrecord Foo [a])

(ancestors Foo)
=> #{clojure.lang.IPersistentCollection clojure.lang.Seqable clojure.lang.ILookup clojure.lang.Associative clojure.lang.IKeywordLookup java.util.Map clojure.lang.IPersistentMap clojure.lang.IObj clojure.lang.Counted clojure.lang.IMeta java.lang.Object java.io.Serializable java.lang.Iterable}

If you need some of that stuff, defrecord is more appropriate. If you need only a named class + IFn, use deftype. If you need only IFn, use reify.

Stu


Stuart Halloway
Clojure/core
http://clojure.com

Sam Aaron

unread,
Jun 18, 2011, 4:44:28 AM6/18/11
to clo...@googlegroups.com
Is it possible to use this approach to create a callable record which can take a variable number of arguments?

I can't get the following to work:

(defrecord Foo [a]
clojure.lang.IFn
(invoke [this & args] (println (str a args))))

(def yo (Foo. "sam"))

(yo 1 2 3 4) ;=> sc-one.Foo.invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; [Thrown class java.lang.AbstractMethodError]

Sam

---
http://sam.aaron.name

Ken Wesson

unread,
Jun 18, 2011, 7:29:29 AM6/18/11
to clo...@googlegroups.com
On Sat, Jun 18, 2011 at 4:44 AM, Sam Aaron <sama...@gmail.com> wrote:
> Is it possible to use this approach to create a callable record which can take a variable number of arguments?
>
> I can't get the following to work:
>
> (defrecord Foo [a]
>  clojure.lang.IFn
>  (invoke [this & args] (println (str a args))))
>
> (def yo (Foo. "sam"))
>
> (yo 1 2 3 4) ;=> sc-one.Foo.invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;   [Thrown class java.lang.AbstractMethodError]

Not easily. You may be able to override applyTo to get that to work.
Alternatively, you can work around it by using destructuring and an
extra vector around the args:

(defrecord Foo [a]
 clojure.lang.IFn
 (invoke [this [& args]] (println (str a args))))

(def yo (Foo. "sam"))

(yo [1 2 3 4])

A bit ugly, but it should work.

David Nolen

unread,
Jun 18, 2011, 10:47:33 AM6/18/11
to clo...@googlegroups.com
On Sat, Jun 18, 2011 at 4:44 AM, Sam Aaron <sama...@gmail.com> wrote:
Is it possible to use this approach to create a callable record which can take a variable number of arguments?

I can't get the following to work:

(defrecord Foo [a]
 clojure.lang.IFn
 (invoke [this & args] (println (str a args))))

(def yo (Foo. "sam"))

(yo 1 2 3 4) ;=> sc-one.Foo.invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;   [Thrown class java.lang.AbstractMethodError]

Sam

defrecord/type methods don't support rest args.

David 

Ken Wesson

unread,
Jun 19, 2011, 9:58:08 AM6/19/11
to clo...@googlegroups.com

Not without some hoop jumping, anyway, but ...

=> (defn my-var-arg-fn [& args] (apply str args))
#'user/my-var-arg-fn
=> (defrecord Foo []
clojure.lang.IFn
(invoke [this] (my-var-arg-fn))
(invoke [this o1] (my-var-arg-fn o1))
(invoke [this o1 o2] (my-var-arg-fn o1 o2))
(applyTo [this, arglist]
(clojure.lang.AFn/applyToHelper my-var-arg-fn arglist)))
user.Foo
=> ((Foo.) "hello" "world")
"helloworld"
=> (apply (Foo.) "hello" "world")
"helloworld"

To really make it work you unfortunately need about 18 more (invoke
...) methods, each with one additional parameter, which is annoying. A
macro could be written to simplify the job. That's for making the
records themselves be functions that accept varargs. To make a random
method do so you'd need to overload it, similarly to the multiple
versions of invoke above, for each arity, or better yet you'd write a
helper function:

(defrecord Foo
SomeProto
(my-meth-impl [this [& args]] ...))

(defn my-meth [some-foo & args]
(my-meth-impl some-foo args))

which just passes the arg seq in as a single, second seq argument to
the record's actual method, which destructures its second argument...

Jonathan Fischer Friberg

unread,
Jun 20, 2011, 9:23:31 AM6/20/11
to clo...@googlegroups.com
Here is said macro:
https://gist.github.com/1035590

user=> (definvokerecord (fn [& args] (apply + args)) ATEST [])
user.ATEST
user=> (ATEST.)
#:user.ATEST{}
user=> (def a (ATEST.))
#'user/a
user=> (a 1 2 3)
6
user=> (a 1 2 3 4 5)
15

I haven't tested it with other protocols, but it should work. ;)

Jonathan

--

Jonathan Fischer Friberg

unread,
Jun 20, 2011, 9:28:24 AM6/20/11
to clo...@googlegroups.com
Maybe I should add how to actually use it:

the first argument is a function which will be called when the record is called.
the rest is simply the arguments to defrecord, as usual.

Jonathan

Jonathan Fischer Friberg

unread,
Jun 20, 2011, 9:38:22 AM6/20/11
to clo...@googlegroups.com
Sometimes I'm just too eager;

I changed the macro so that the first argument to the function is the current record (i.e. "this").

user=> (definvokerecord (fn [this & args] (apply + args)) ATEST [])
user.ATEST

user=> (def a (ATEST.))
#'user/a
user=> (a 1 2 3)
6
user=> (a 1 2 3 4 5)
15

Jonathan

Ryan Twitchell

unread,
Jun 20, 2011, 11:33:17 AM6/20/11
to Clojure
Most notably, reify does not "def" anything. It's very in-tune with
functional programming in that way, as it has no side-effects, whereas
defrecord adds a class to the namespace.

reify has been compared to Java's anonymous classes. A good example
of its use might be in implementing a factory method:

(defn make-listener [name]
(reify java.awt.event.ActionListener
(actionPerformed [listener action-event]
(prn name action-event))))

(actionPerformed (make-listener "my-listener") some-action-event)
"my-listener" #<ActionEvent ...>

Note that the object created with reify captures the "name" argument
passed to the factory function.

Ryan

On Jun 15, 6:59 pm, Michael Nygard <mtnyg...@gmail.com> wrote:
> mtnyg...@gmail.comhttp://www.michaelnygard.com/
>
> Release It! Design and Deploy Production-Ready Softwarehttp://bit.ly/ReleaseIt
>
> Beautiful Architecturehttp://bit.ly/BeautifulArchitecture
Reply all
Reply to author
Forward
0 new messages