Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure's funcs don't seem to work as expected when sent thru a list

Tags:

clojure

At the Clojure REPL, this expression

( #(for [x %] (+ 100 (second x)))  ['(+ 38) '(+ 48)] )

produces (138 148) as expected

but this

( #(for [x %] ((first x) 100 (second x))) ['(+ 38) '(+ 48)] )

produces (38 48) which seems truly weird.

Both expressions really should be producing the same result! What am I missing? Will appreciate any ideas to resolve this mystery.

BTW, I tried to use 'apply (first x)' and package the rest of the args into a list but it doesn't seem to matter. The same unexpected result comes back.

Also, to verify that the + indeed gets resolved from the input, I gave the following to the REPL

( #(for [x %] (resolve (first x) )) '((+ 38) (+ 48)) )

which produced

 (#'clojure.core/+ #'clojure.core/+)   as expected.
like image 210
Don Avatar asked Dec 13 '22 07:12

Don


2 Answers

( #(for [x %] ((first x) 100 (second x))) ['(+ 38) '(+ 48)] )

In this the + is a symbol, not a function, because it has been quoted in the list. However, symbols are defined as doing map lookup when invoked as a function (the same as keywords). So ('+ 100 38) is the same as (get 100 '+ 38). That last argument is the "if you can't find what I want in the map, return that". Since 100 is not a map, + uses that argument as the return value.

To make it do what you want you have two options:

  1. Use vectors instead of quoted lists ensures that + gets resolved appropriately.

    ( #(for [x %] ((first x) 100 (second x))) [[+ 38] [+ 48]] )
    
  2. Resolve it yourself to ensure that you use the + function instead of the + symbol.

    ( #(for [x %] ((resolve (first x)) 100 (second x))) ['(+ 38) '(+ 48)] )
    
like image 156
mange Avatar answered Dec 14 '22 22:12

mange


When you quote a list, like '(+ 38), none of the items in the list are evaluated. Thus the + is just a symbol and not a reference to the addition function from clojure.core.

The result of calling this symbol as a function is a bit confusing, especially since you happen to call it with exactly two arguments. The reason was already explained by @mange: Invoking a symbol as a function tries to look up the symbol in the first argument, returning the (optional) second argument as a default when the lookup fails:

('x)       ; throws ArityException
('x 1)     ;=> nil
('x 1 2)   ;=> 2
('x 1 2 3) ; throws ArityException

You have several options:

  1. Use vectors instead of quoted lists: [+ 38]. All elements of a vector are evaluated (as in an unquoted list), but the vector is just a data structure, not the syntax for function invocation as is the list.
  2. Use the resolve function to find the function referenced by the symbol. Note that resolve looks up the symbol in the current namespace. So if the construction of the quoted list and the invocation of the function happen in different namespaces, this may lead to surprising results (if you happen to have two different definitions for the same symbol in the different namespaces).
  3. Use syntax quoting instead of simple quoting on the list and unquote the symbol (evaluating it): `(~+ 38)
like image 40
Christian Berg Avatar answered Dec 14 '22 20:12

Christian Berg