Arne
Arne

Reputation: 2674

In what way are Clojure maps functions?

In his talk "Maybe not" Rich Hickey states:

maps are (mathematical) functions!

in Clojure, we can directly write, and invoke

({:a 1 :b 2} :b) => 2

However I have the feeling that they are not in fact first class Clojure functions, or are they?

I can call the map with a keyword, or the other way around:

user=> (:b {:a 1 :b 2 :c 3})
2
user=> ({:a 1 :b 2 :c 3} :b)
2

But I can't use apply either way it seems:

user=> (apply #(:b %) {:a 1 :b 2 :c 3})
ArityException Wrong number of args (3) passed to: user/eval1762/fn--1763  clojure.lang.AFn.throwArity (AFn.java:429)

user=> (apply #({:a 1 :b 2 :c 3} %) :b)
IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Keyword  clojure.lang.RT.seqFrom (RT.java:542)

And neither can I apply the keyword directly to the map:

user=> (apply {:a 1 :b 2 :c 3} :b)
IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Keyword  clojure.lang.RT.seqFrom (RT.java:542)

So are they functions only in the mathematical sense, or is there more to them in the sense of applying a keyword similar to a "normal" clojure function?

Upvotes: 5

Views: 767

Answers (3)

Thumbnail
Thumbnail

Reputation: 13483

A mathematical function is a set of ordered pairs such that there are no distinct pairs with the same first element. A mathematical function so defined takes only one argument.

Clojure maps are self-evidently mathematical functions, since the there are no distinct key-value pairs with the same key. Moreover, Clojure maps have a finite domain (set of keys) and range/co-domain (set of values).

As @TaylorWood has stated, Clojure maps are Clojure functions because they implement clojure.lang.IFn. They do so as operators yielding the value for a given key, so consistent with their interpretation as mathematical functions.

Your apply syntax is wrong. You can write

user=> (apply {:a 1, :b 2} [:a])
1

or

user=> (apply {:a 1, :b 2} :a []) 
1

You can supply some of the arguments for the function (the first argument to apply) inline. The remainder comprise the sequence that is the last argument to apply. Since there is exactly one argument here, we can put it inline or alone in the terminal sequence.

Similar considerations apply to the use of keywords as arguments.


Reply to @AlanThompson's comments

  1. I've listened to and read Rich's talk that the question refers to. My definition of a function agrees with his.

  2. My statement that "A mathematical function so defined takes only one argument" is self evidently correct.

  3. You could contend that my definition isn't the conventional one. But even that is false. If you read the Multivariate_function part of the Wikipedia article we both refer to, you will find

More formally, a function of n variables is a function whose domain is a set of n-tuples

...

When using function notation, one usually omits the parentheses surrounding tuples, writing f(x1 , x2) instead of f((x1 , x 2)).

In other words, the idea that a (mathematical) function takes several arguments is a harmless informality that allows simpler notation.

Where does that leave programming languages? Most of them have functions of any arity: in Clojure's case, any finite set of arities, including an infinite one.

The simplest way to regard Clojure functions is that they take an inline sequence of arguments, perhaps an empty one. It would be possible to formulate mathematical functions in this way: mappings from sequences of things to things. But it just isn't done.

Upvotes: 8

Alan Thompson
Alan Thompson

Reputation: 29984

I'm not sure what the practical goal of this question is, but it is interesting from an academic point of view.

The keyword "function" :b in the following is a function of 1 argument:

(:b {:a 1 :b 2})  => 2

It is like this function:

(defn get-b [m] (get m :b)) ; takes a single map arg

(get-b {:a 3 :b 33})  ; only 1 arg, so no problem
   => 33

The special form apply re-writes the function call from having a single collection argument to having multiple, individual args. Comparing before & after we get:

(apply some-fn [1 2 3] )  ; using  apply
      (some-fn  1 2 3  )  ; without apply (note missing square brackets!)

So, apply allows you to "spread" args from a collection as if you had typed them as separate, individual args in the function call. If your collection has only 1 element, using apply has little effect:

(apply :b [{:a 3 :b 33}] ) ; a collection of 1 element
      (:b  {:a 3 :b 33}  ) ; only 1 arg, so no problem

Because the function :b takes only 1 arg, the following won't work:

(apply #(:b %) [{:a 1 :b 11} {:a 2 :b 22} {:a 3 :b 33}])  ; takes only 1 arg, not 3 args

However, you can invoke the 1-arg function :b multiple times using map or mapv:

(mapv :b [{:a 1 :b 11} {:a 2 :b 22} {:a 3 :b 33}])  ; takes only 1 arg, not 3 args

   => [11 22 33]

The same rules hold for the inverse case. A map like {:a 1 :b 2} is a function of 1 argument, so it can't be invoked with 3 args:

(apply {:a 1, :b 2, :c 3} [:c :b :a])  ; 3 args to single-arg fn, so fails

but this works fine:

(mapv {:a 1, :b 2, :c 3} [:c :b :a]) => [3 2 1]

Upvotes: 4

Taylor Wood
Taylor Wood

Reputation: 16194

Maps are functions of keys, as in your first two examples. Maps implement IFn interface just like "regular" Clojure functions.

The reason apply doesn't work in your examples is due to the "sequence" arguments being passed in the second position.

(apply #(:b %) {:a 1 :b 2 :c 3})

In that example, the map argument is being turned into a sequence of key/value vectors/tuples, so they can be applied to #(:b %) (which wouldn't work anyway because that anonymous function only takes one argument). This is how the map would look as it's being turned into a sequence and applied as arguments to the function:

user=> (seq {:a 1 :b 2 :c 3})
([:a 1] [:b 2] [:c 3])

This second example doesn't work because :b is not a sequence — it's a single keyword. This works though:

user=> (apply {:a 1 :b 2 :c 3} [:b])
2

Note that calling a map-as-function with apply and a sequence of keywords doesn't really make practical sense though.

Upvotes: 9

Related Questions