I’m stuck on ch 5 (fn programming) ex. #2 implement comp function.
I assume I need to use loop/recur or reduce, but I can’t figure out how to properly handle the arguments
Actually not, in this case. There's an idiom that shows up fairly commonly in Clojure code (and in functional code in general) that uses multiple arities to break an arbitrary number of arguments down into something manageable, using recursion.
Arity, in case you're not familiar, is just a reference to the number of arguments a function accepts. Here's a multiple-arity Clojure function that takes either 1 or 2 arguments:
(defn foo
([x] (foo x 0))
([x y] (+ x y)))
In this case, the 1-arity version of the function calls the 2-arity version, so it's going from a smaller number of arguments to a larger number. But you can go the other way as well, and have a bigger-arity function call a smaller-arity function.
I'm not going to give you any more hints than that because it's probably more useful for you to wrestle with this a bit. Well, just one more hint: Here's a function that takes any number of arguments:
(defn foo2 [x y & z]
;; do something with x y and z
)
If you call (foo2 :a :b)
, then inside the body of foo2
, x
will be :a
, y
will be :b
, and z
will be []
.
For (foo2 :a :b :c :d)
you get x
as :a
, y
as :b
and z
as [:c :d]
.
So any number of additional arguments will fit in z
.
The array just gets bigger the more args you add.
That should be enough to let you build your own comp
function that takes any number of arguments, but you'll need to think functionally/recursively to figure it out.
(I'll be honest, I think I would have wrestled with this one for a while when I was first learning FP...)
what is tricking me, is that I’m sure I shouldn’t be checking the type. But I can’t figure out how to separate an arbitrary number of 1st class functions from their parameters.
for example
(defn my-2comp [x y] (x (y))
Ah, there’s a catch to comp
that will simplify things greatly. Only the last (rightmost) fn can take 0-n arguments. All the other fns must take only a single argument, which will be the result returned by the fn on the right.
ahhh!
The comp
fn itself accepts only fns as arguments.
so I’m trying to make (fn1(fn2(fn2(all the other args))))
For your my-comp
functions, the only arguments my-comp
will need to worry about are the fn1
, fn2
, fn3
etc args. not the all the other args
oh I won’d need to worry about the args because this works:
(def my-plus +)
(my-plus 4 5)
gotcha
that was getting me
comp
will return a function that you can then call with all the other arguments
actually I’m still confused because
Right, you’re “composing” multiple functions into a single function
so the arguments to that list of functions will be applied later, after you get done composing them.
(defn my-plus [] +)
needs to worry about args but (def my-plus +)
doesn’t
Ok, look at how the built-in comp fn works:
(def double-inc (comp inc inc))
(double-inc 1) ;; ==> 3
Notice we’re using def
not defn
, and we’re passing two bare functions to comp
(they happen to be the same function, but that’s ok)
maybe I’m confused about def
vs defn
Ok
How comfortable are you with anonymous functions?
can I use defn
to make an inc
function factory?
(fn [] (do stuff))
and #(do %1)
Ok cool. So all defn
is really doing is just giving you some syntactic sugar for something like this:
(def my-fn
(fn [a b] (+ a b)))
The (fn [a b] (+ a b))
is an anonymous function that takes 2 args and returns the sum. The def
takes that function, and assignss it to a name so that you can call (my-fn 1 2)
defn
is binding a name to a function and def
is just binding a name to something?
def
in other words, takes a value and assigns a name to it. In clojure, anonymous functions are also values, just like numbers and strings are values. So you can assign them to vars using def
Right, defn
is just a shortcut that lets you define a function and give it a name all in one step
okay that helps, what is different between:
(def my-plus +) ;; which works for any number of parameters and
(defn my-plus [a b] ; gotta put something here in [ ]
(+ a b)) ; but this only works for two params
Right.
I feel like once I get how to define a plus function,… I can tackle comp
🙂
The +
symbol right now is a name that points to a function that does addition. If you type +
by itself you’ll get the value of +
, which is an anonymous function. Since it’s a value, you can assign it to other vars as well, which is why (def my-add +)
works.
that part I understand
With def
we’re just binding a new name to a function
the part I don’t understand, is how to define a my-plus
with defn
because then I imagine I’ll need to deal with parameters
and the right answer doesn’t feel like it involves tailing off the last parameter with recur
but maybe it does?
I’m clearly missing something basic
Sorry, got called away for a bit
So probably the part you’re missing is how to use multi-arity functions to do the same sort of thing as loop/recur
Here’s the (simplified) source code for the +
function:
(defn +
"Returns the sum of nums. (+) returns 0....'"
;; metadata stuff, skipped
([] 0)
([x] (cast Number x))
([x y] (. clojure.lang.Numbers (add x y)))
([x y & more]
(reduce1 + (+ x y) more)))
So we’ve got a zero-arity version, a single-arity version, a 2-arity version, and a more-than-2-arity version.
The 2-arity version is making actual Java interop calls to do the actual adding.
Do you have an example without reduce?
because (reduce + all the args) would work, so … I missed the magic somehow
I might be able to come up with one, but let’s look at this one for just a minute
(if that makes sense)
k, I’m with ya
The trick here is that this reduce is actually happening inside the definition for +
also it’s reduce1
and not reduce
oh
Yeah we’re digging into the innards of how Clojure works
we’re either brave or dumb 😆
for clarity is this equiv?
(defn my-plus
"Returns the sum of nums. (+) returns 0....'"
;; metadata stuff, skipped
([] 0)
([x] (cast Number x))
([x y] (. clojure.lang.Numbers (add x y)))
([x y & more]
(reduce1 my-plus (my-plus x y) more)))
brave and true right 😉
Yeah, that should work, but how does it work?
great now I won’t wonder about built-in +, okay, thinking about it now
Checking out reduce1… https://github.com/clojure/clojure/blob/clojure-1.7.0/src/clj/clojure/core.clj#L895
Ok, actually I’d say don’t look at that
k skipping
It’s doing a bunch of extra stuff we don’t want to mess with for now
oh reduce1 is eating one element
like this:
Yeah, it kind of works that way
let’s step thru it with (+ 1 2 3 4)
(reduce1 my-plus
(my-plus x y) ; combine two elements into one by calling the 2-arity)
more))) ; now more is one shorter?
k let’s step
I’m going to use the built in source for +
because it’s shorter to type 🙂
so at the beginning, we are calling the [x y & more]
k gotcha
Yup, and what values do x, y, & more have here?
so thats (+ 1 2 [3 4])
wait
now that’s right
You got the right idea, but I wouldn’t write it like that, I’d just say x=1, y=2 and more=[3, 4]
(reduce + (+ 1 2) [3 4]); (+ 1 2) ==> is 3 so that gives:
(+ 3 3 [4])
(+ 6 4)
(+ 10)
10
?
Did I unwind that right?
Right. Notice that in the middle, the (+ 1 2)
is a call to the 2-arity version. That’s the recursion trick--you know how to handle 2 args, so you use that arity to reduce 2 args down to 1, and now your list of arguments is one shorter.
Oh man that was super helpful!
You went an extra step or two — once the list of arguments is down to 2, you just hit the 2-arg version and don’t need to keep recursing any more.
oh (+ 10) isn’t called
right
that’s a special case for one arg
Yeah, special cases for zero arity and 1-arity.
Was it (+ 3 3 [4])
or (+ 3 3 4)
?
is more
always a vector because of &
?
The arguments to +
will be the numbers without the vector — yes, the &
is what makes more
a vector
yes, I can add. Thanks. Okay, I’ll go mess around in the repl until comp
plops out. 🙂
I think reduce1
is converting it back to the (+ 3 3 4)
form again somehow
but I didn’t follow the code all the way thru to understand exactly how
Alrighty, have fun and good luck 🙂
So, to summarize what I understand
Actually crap, I”m still missing something w.r.t. defn and parameters
I see now how we can handle n-arity for actually doing something
and I see how I could return f1(f2())
but if we make g=f1 of f2, I don’t see how to call g(more)
Yeah, there’s a trick to thinking recursively
k, … off to puzzlement 🙂
Let me see if I can give you one more nudge in the right direction by writing out a verbal description of what +
is supposed to do. Actually, let me call it sum
, it will read easier in english:
(sum) with zero arguments should return 0
(sum n) with 1 arg should return the arg
(sum n1 n2) with 2 args should return the result of adding them <== the ending case, does not recurse!
(sum n1 n2 n3...) with more than 2 args should return
the sum of
(the sum of the first 2 args) and
the remaining numbers
So the recursive thinking is in the last case where we’ve defined the sum
of a big list of numbers in terms of finding the sum
of 2 smaller lists of numbers. Since the list gets smaller each time we recurse, we know we’ll eventually reach the end case of only 2 arguments.Yep that makes sense
but, let’s take something non-recursive to show where I’m stuck, a comp function that only takes two functions
(defn my-2comp [f1 f2] (f1 (f2)))
do I need to handle the arguments to f2 in my-2comp for it to work?
like, do I need to build a tail-recursion part to handle inputs to the final function or am I missing something else
Hmm, actually let me think about that for a minute
Ok, there’s one more piece you’re going to need, and that’s the opposite of & more
.
I thought maybe I needed to destructure last and second to last or something.
Actually, we’ve reached the point where the problem you’re working on is making me have to stop and think
because it feels like I need to eat the tail not the head
🙂
You’re on the right track, because you definitely need the right-most function to be the first one that actually executes
I wish you could do this [[& first-fns f-second-to-last f-last]]
like the opposite of [[f1 f2 & rest]]
back to my-2comp at the repl… 🙂
actually, I just don’t get what happens to the args of g for example
Ok, I’d suggest tackling a simpler version first. Write my-comp
assuming that it will only ever be given functions that take exactly 1 argument.
(defn my-2comp [f1 f2 & more] (f1 (f2 more)))
is wrong because 2-comp should only take two functions
maybe I should start with my-1comp
(defn my-1comp [f] f)
works
The other thing you’re missing is that you’re writing my-2comp
such that it takes 2 functions and returns the result of calling those functions, which is wrong
(def test-fn (my-1comp -))
(test-fn 0 5)
;; --> -5
oh, (defn my-1comp [f] f)
vs (defn my-1comp [f] (f))
Your my-1comp
is returning a function, which is correct. It’s not calling the function, it’s just returning it
so your first version of my-1comp
is returning the correct type of result.
I see that’s why this happens:
(defn my-1comp [f] (f))
(def test-fn (my-1comp -))
; --> CompilerException clojure.lang.ArityException: Wrong number of args (0) passed to: core/-, compiling
right
my-1comp
(first version) works because you only have one argument, so you can just return it.
but this doesn’t work:
(defn my-2comp
([f] f)
([f1 f2] (f1 f2)))
I don’t see why
That’s not working because it’s returning the result of calling f1
on f2
.
so I need to suspend evaluation?
Right, and the way you do that is by returning an anonymous function (fn [...] ...)
I see
that’s how to be lazy in this case
You could put it that way (but “lazy” usually refers to something slightly different in Clojure)
that’s how I return a 1st class function instead of returning the immediate result of applying that fn
Exactly
so it appears like I’ll need to overload that anon function for any arity
Correct (which is why I recommend starting off with only comp-ing single-arity functions)
okay
Once you have a comp
that can compose any number of single-arity functions, you can take that solution and adapt it to return a multi-arity anonymous function.
I think i have enough to puzzle it out. There is the composition and the arguments and the returning of a fn. So that’s 3 parts.
yeah, build it up
Thank you!!!
well, I found one way to solve my wrapping issue
(defn my-1comp
([f]
(fn
([] f)
([a1] (f a1))
([a1 & rest] (apply f a1 rest)))))
apply
works and does the wrapping. I suppose I ’ought to implement apply
after this to make sure I get that part too.
Looks good except the zero-arity version of your anonymous function is wrong. Since you’re inside the anonymous function, you do need to call f
. Your 1-arity and more-than-1-arity versions are calling f
correctly, you just need to have the zero-arity version call it too (with no args, of course, since it’s zero-arity)
but your my-1comp
function is doing the right thing by returning an anonymous function instead of having my-1comp
try to call f
directly.
it’s working somehow
(however, I of course believe you)
oh wait, no I didn’t test it correctly
so it should be (f) for that zero-arity
right
The reason (f)
was wrong before was because in the earlier versions it wasn’t inside a (fn [...] ...)
Now that my-1comp
is returning an anonymous function, you need to have the anonymous function actually call it.
I tested with with (def myprintln (my-1comp println)
with the f
version calling (my-println)
returns an object ref to the fn, whereas (f)
does what it should
that makes sense
:thumbsup: Gotta sign off for a bit, I’ll check in later and see how its going
Thanks!