Memfn: what does it mean?

94 views
Skip to first unread message

Garth Sheldon-Coulson

unread,
Aug 3, 2009, 2:44:04 AM8/3/09
to clo...@googlegroups.com
Out of curiosity, does anyone know what memfn stands for? Is it an abbreviation? Fn obviously stands for function. Mem calls to mind memoize, but I'm not sure I see how memfn memoizes anything...

Mike DeLaurentis

unread,
Aug 3, 2009, 8:53:44 AM8/3/09
to clo...@googlegroups.com
I believe it stands for "member function". From the doc string:

"Expands into code that creates a fn that expects to be passed an
object and any args and calls the named instance method on the
object passing the args. Use when you want to treat a Java method as
a first-class fn."

Garth Sheldon-Coulson

unread,
Aug 3, 2009, 9:08:53 AM8/3/09
to clo...@googlegroups.com
Ah, makes sense. Thanks.

John Harrop

unread,
Aug 3, 2009, 2:10:00 PM8/3/09
to clo...@googlegroups.com
On Mon, Aug 3, 2009 at 8:53 AM, Mike DeLaurentis <delau...@gmail.com> wrote:

I believe it stands for "member function". From the doc string:

 "Expands into code that creates a fn that expects to be passed an
 object and any args and calls the named instance method on the
 object passing the args. Use when you want to treat a Java method as
 a first-class fn."

Interestingly, it's just a very simple macro to automate wrapping the method call in an anonymous lambda:

user=> (macroexpand-1 '(memfn add x y))
(clojure.core/fn [target__4193__auto__ x y] (. target__4193__auto__ (add x y)))

That is, basically (fn [object x y] (.add object x y)).

I'd guessed as much even before trying the above macroexpand-1 line. It was the obvious way to implement it with no magic required. (I think you'll find that most of the magic in Clojure is ClassLoader voodoo quite far under the hood, and that a lot of seemingly fancy features are simply implemented as fairly basic macros. The ".add" type of method call, however, does seem to be some sort of serious mojo. (doc .add) throws an exception rather than recognizing it as a special form or similarly, though you also get strange errors if you try to use .add as a variable name.)

Richard Newman

unread,
Aug 3, 2009, 3:45:42 PM8/3/09
to clo...@googlegroups.com
> user=> (macroexpand-1 '(memfn add x y))
> (clojure.core/fn [target__4193__auto__ x y] (. target__4193__auto__
> (add x y)))
>
> That is, basically (fn [object x y] (.add object x y)).

.add is macroexpanded into the more general dot form, as shown in the
memfn expansion.

user=> (read-string "(.foo bar)")
(.foo bar)
user=> (macroexpand-1 *1)
(. bar foo)

http://clojure.org/java_interop#dot


> (doc .add) throws an exception rather than recognizing it as a
> special form

doc looks up the docstring of a var. If the argument isn't a var, it
will throw an exception.

If you break your contract with Clojure and make a var with a dot-name
anyway, doc will work:

user=> (def .add 5)
#'user/.add
user=> (doc .add)
-------------------------
user/.add
nil
nil
nil

> or similarly, though you also get strange errors if you try to
> use .add as a variable name.)

"Symbols beginning or ending with '.' are reserved by Clojure.".

http://clojure.org/reader

You don't get errors if you use .add as a var or the name of a symbol
(see above), though you're disobeying the docs.

You do get an error if you use it as the name of a local binding symbol:

user=> (let [.foo 5])
java.lang.ClassFormatError: Illegal field name ".foo" in class user
$eval__10 (NO_SOURCE_FILE:0)

When you think about what this is actually doing in terms of Java
classes, this message makes sense.

-R

John Harrop

unread,
Aug 3, 2009, 4:10:40 PM8/3/09
to clo...@googlegroups.com
On Mon, Aug 3, 2009 at 3:45 PM, Richard Newman <holy...@gmail.com> wrote:

> user=> (macroexpand-1 '(memfn add x y))
> (clojure.core/fn [target__4193__auto__ x y] (. target__4193__auto__
> (add x y)))
>
> That is, basically (fn [object x y] (.add object x y)).

.add is macroexpanded into the more general dot form, as shown in the
memfn expansion.

user=> (read-string "(.foo bar)")
(.foo bar)
user=> (macroexpand-1 *1)
(. bar foo)

So, it treats ".foo" in form-initial position as a macro, though not elsewhere (so "(doc .foo)" does not produce "Macro." and other data).

Plain "." is reported as a "Special Form" by doc.

It is definitely interesting sometimes to poke at the guts of Clojure in the REPL. :)

One apparent deficiency brought to light by all of this: since "memfn" is a macro, it's not itself composable. Other macros where I've found this to occasionally be a problem are "and" and "or" (where I wanted to do something like (reduce and (map pred? coll))) but it's easy enough to make function versions of all of them:

(defn and-ns
  "A non-short-circuiting \"and\" usable in reduce etc."
  ([] true)
  ([a] a)
  ([a b] (and a b))
  ([a b & more] (reduce and-ns (and a b) more)))

(defn or-ns
  "A non-short-circuiting \"or\" usable in reduce etc."
  ([] false)
  ([a] a)
  ([a b] (or a b))
  ([a b & more] (reduce or-ns (or a b) more)))

(defn make-memfn
  "Given a quoted method name and a quoted vector of argument names, returns a function that can be composed and that takes a target object plus the named arguments and invokes the named method on that target with those arguments."
  [name arg-vec]
  (eval `(fn [target# ~@arg-vec] (. target# (~name ~@arg-vec)))))

Of course, the first two don't short-circuit like their macro counterparts, and the latter needs its arguments quoted. The latter generates:

user=> (make-memfn 'add '[x y])
(clojure.core/fn [target__15__auto__ x y] (. target__15__auto__ (add x y)))

(obtained by dropping the eval from make-memfn, so it just returned the backquoted expression; compare with memfn above).

Of course you're probably doing some serious voodoo if you are generating memfns on the fly from run-time-determined names and argument vectors. Metaprogramming, or making some kind of Java-object inspector, or whatever. The function versions of and and or are much more generally useful, since reductions over maps of predicates arise from time to time in more ordinary situations.

Meikel Brandmeyer

unread,
Aug 4, 2009, 2:22:48 AM8/4/09
to Clojure
Hi,

On Aug 3, 10:10 pm, John Harrop <jharrop...@gmail.com> wrote:

> (defn and-ns
>   "A non-short-circuiting \"and\" usable in reduce etc."
>   ([] true)
>   ([a] a)
>   ([a b] (and a b))
>   ([a b & more] (reduce and-ns (and a b) more)))

Don't think to complicated.

(reduce #(and %1 %2) coll)

This should do the trick, no? It's still non-short-circuiting, though.

I think memfn is some relict like .., which pre-dates some
of the nice additions to clojure.

#(.foo %1 %2 fixed-arg %3)

That should fill all the needs, which might come up. (Besides
an unknown method name, but for that you'll need reflection,
AFAIK...)

Sincerely
Meikel

John Harrop

unread,
Aug 4, 2009, 2:45:36 AM8/4/09
to clo...@googlegroups.com
On Tue, Aug 4, 2009 at 2:22 AM, Meikel Brandmeyer <m...@kotka.de> wrote:

Hi,

On Aug 3, 10:10 pm, John Harrop <jharrop...@gmail.com> wrote:

> (defn and-ns
>   "A non-short-circuiting \"and\" usable in reduce etc."
>   ([] true)
>   ([a] a)
>   ([a b] (and a b))
>   ([a b & more] (reduce and-ns (and a b) more)))

Don't think to complicated.

       (reduce #(and %1 %2) coll)

This should do the trick, no? It's still non-short-circuiting, though.

That will do the trick, but it's kind of ugly compared to just having a symbol for the first argument to reduce.
 
I think memfn is some relict like .., which pre-dates some
of the nice additions to clojure.

       #(.foo %1 %2 fixed-arg %3)

What, you mean it predates the #(...) read-macro?
 
I wouldn't know; I only started using Clojure when version 1.0 became available.

Rich Hickey

unread,
Aug 4, 2009, 8:52:40 AM8/4/09
to clo...@googlegroups.com
On Tue, Aug 4, 2009 at 2:22 AM, Meikel Brandmeyer<m...@kotka.de> wrote:
>

Yes, memfn preceded #(), the latter now being preferred.

Rich

Reply all
Reply to author
Forward
0 new messages