tonino.j
tonino.j

Reputation: 3871

clojure partial clarification

I'm reading a book on clojure, and I came by an example that I dont fully understand..

Here is the code in repl:

user=> (repeatedly 10 (rand-int 10))
ClassCastException java.lang.Integer cannot be cast to clojure.lang.IFn  clojure.core/repeatedly/fn--4705 (core.clj:4642)

user=> (repeatedly 10 (partial rand-int 10))
(5 0 5 5 2 4 8 8 0 0)

My question is: why is the partial needed here, and how does that fit into partial definition, and repeatedly definition & syntax. Partial ...

Takes a function f and fewer than the normal arguments to f, and
  returns a fn that takes a variable number of additional args. When
  called, the returned function calls f with args + additional args.

So how does this fit in?

Upvotes: 9

Views: 2255

Answers (3)

yuranos
yuranos

Reputation: 9695

repeatedly signature we are interested in: (repeatedly number function)

In this case partial will simply wrap rand-int 10 into a function that can be returned and used by the outer function, in this case repeatedly.

Without partial(or #) inner expressions are resolved before the outer ones(there are exceptions, but let's keep it simple for now), so when repeatedly is called without partial, what's gonna be passed to it is a return value of rand-int, that is Int and not a function.

Upvotes: 0

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91554

Partial is just an easier way of defining an anonymous function that fixes some of the arguments to a function and then passes the rest from the arguments to the created function.

In this case

user> (repeatedly 10 (partial rand-int 10))
(3 1 3 6 1 2 7 1 5 3)

is equivalent to:

user> (repeatedly 10 #(rand-int 10))                        
(9 5 6 0 0 5 7 6 9 6)

Partial is a misnomer here because partial is being used to fix in advance all the arguments (or rather the only one) to rand-int.

a more intersting use of partial illustrates it's function better:

(partial + 4)

produces the equivalent of:

(fn [& unfixed-args] (apply + (concat [4] unfixed-args)))

(it does not literally produce this) The idea being to build a function that takes the un-fixed arguments, combines them with the fixed ones, and calls the function you passed to partial with enough arguments to work properly.

user> ((fn [& unfixed-args] (apply + (concat [4] unfixed-args))) 5 6 7 8 9)       
39
user> ((partial + 4) 5 6 7 8 9)
39   

I only use partial in practice when the number of arguments is variable. Otherwise I have a personal preference towards using the anonymous function reader form #( ... )

Upvotes: 11

Michał Marczyk
Michał Marczyk

Reputation: 84341

partial does not actually check which arities its first argument supports; an arguably more accurate docstring would say that it "takes a function f and some arguments to f". (Clearly if you supply too many arguments, the resulting partially applied function will be broken, though that will only be observable when you try to call it.) So that's why (partial rand-int 10) is ok even though the number of arguments to rand-int supplied is not "fewer than normal".

The reason why either partial or something like #(rand-int 10) is needed here is that repeatedly expects its final argument to be a function which it can repeatedly call, whereas (rand-int 10) would be a number.

Compare this to repeat which returns a sequence with the supplied item repeated the specified number of times (or infinitely many times in the unary case). Here (rand-int 10) would be an appropriate second argument, but of course it would be some particular number, so the result would look like (8 8 8 8 8 ...); repeatedly will make a separate call to (partial rand-int 10) for each item of the sequence returned, so you'll get from it a sequence of (likely different, independent) random numbers.

Upvotes: 7

Related Questions