braveandtrue

https://www.braveclojure.com/
moo 2018-08-28T01:29:34.000100Z

I’m stuck on ch 5 (fn programming) ex. #2 implement comp function.

moo 2018-08-28T01:30:37.000100Z

I assume I need to use loop/recur or reduce, but I can’t figure out how to properly handle the arguments

2018-08-28T02:14:09.000100Z

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.

2018-08-28T02:16:32.000100Z

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)))

2018-08-28T02:18:35.000100Z

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.

2018-08-28T02:20:24.000100Z

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
  )

2018-08-28T02:21:18.000100Z

If you call (foo2 :a :b), then inside the body of foo2, x will be :a, y will be :b, and z will be [].

2018-08-28T02:23:08.000100Z

For (foo2 :a :b :c :d) you get x as :a, y as :b and z as [:c :d].

2018-08-28T02:23:32.000100Z

So any number of additional arguments will fit in z.

2018-08-28T02:23:46.000200Z

The array just gets bigger the more args you add.

2018-08-28T02:24:29.000100Z

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.

2018-08-28T02:30:59.000100Z

(I'll be honest, I think I would have wrestled with this one for a while when I was first learning FP...)

moo 2018-08-28T14:47:36.000100Z

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.

moo 2018-08-28T14:47:39.000100Z

for example

moo 2018-08-28T14:50:55.000100Z

(defn my-2comp [x y] (x (y))

2018-08-28T14:50:59.000100Z

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.

moo 2018-08-28T14:51:17.000100Z

ahhh!

2018-08-28T14:51:17.000200Z

The comp fn itself accepts only fns as arguments.

moo 2018-08-28T14:51:48.000100Z

so I’m trying to make (fn1(fn2(fn2(all the other args))))

2018-08-28T14:53:06.000100Z

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

moo 2018-08-28T14:53:16.000100Z

oh I won’d need to worry about the args because this works:

(def my-plus +)
(my-plus 4 5)

moo 2018-08-28T14:53:37.000100Z

gotcha

moo 2018-08-28T14:53:39.000100Z

that was getting me

2018-08-28T14:53:48.000200Z

comp will return a function that you can then call with all the other arguments

moo 2018-08-28T14:54:21.000100Z

actually I’m still confused because

2018-08-28T14:54:21.000200Z

Right, you’re “composing” multiple functions into a single function

2018-08-28T14:54:40.000200Z

so the arguments to that list of functions will be applied later, after you get done composing them.

moo 2018-08-28T14:54:51.000100Z

(defn my-plus [] +) needs to worry about args but (def my-plus +) doesn’t

2018-08-28T14:56:52.000100Z

Ok, look at how the built-in comp fn works:

(def double-inc (comp inc inc))
(double-inc 1) ;; ==> 3

2018-08-28T14:57:53.000100Z

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)

moo 2018-08-28T14:58:18.000100Z

maybe I’m confused about def vs defn

2018-08-28T14:58:36.000100Z

Ok

2018-08-28T14:58:49.000100Z

How comfortable are you with anonymous functions?

moo 2018-08-28T14:58:58.000100Z

can I use defn to make an inc function factory?

moo 2018-08-28T14:59:54.000100Z

(fn [] (do stuff)) and #(do %1)

2018-08-28T15:01:10.000100Z

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)))

2018-08-28T15:02:10.000200Z

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)

moo 2018-08-28T15:02:36.000100Z

defn is binding a name to a function and def is just binding a name to something?

2018-08-28T15:03:13.000100Z

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

2018-08-28T15:03:47.000100Z

Right, defn is just a shortcut that lets you define a function and give it a name all in one step

moo 2018-08-28T15:06:22.000100Z

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

2018-08-28T15:06:58.000100Z

Right.

moo 2018-08-28T15:07:06.000100Z

I feel like once I get how to define a plus function,… I can tackle comp

moo 2018-08-28T15:07:07.000300Z

🙂

2018-08-28T15:08:47.000100Z

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.

moo 2018-08-28T15:09:43.000100Z

that part I understand

moo 2018-08-28T15:09:53.000100Z

With def we’re just binding a new name to a function

moo 2018-08-28T15:10:39.000200Z

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

moo 2018-08-28T15:11:05.000100Z

and the right answer doesn’t feel like it involves tailing off the last parameter with recur

moo 2018-08-28T15:12:27.000100Z

but maybe it does?

moo 2018-08-28T15:14:29.000100Z

I’m clearly missing something basic

2018-08-28T15:25:38.000100Z

Sorry, got called away for a bit

2018-08-28T15:26:25.000100Z

So probably the part you’re missing is how to use multi-arity functions to do the same sort of thing as loop/recur

2018-08-28T15:29:03.000100Z

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)))

2018-08-28T15:30:22.000100Z

So we’ve got a zero-arity version, a single-arity version, a 2-arity version, and a more-than-2-arity version.

2018-08-28T15:31:05.000100Z

The 2-arity version is making actual Java interop calls to do the actual adding.

moo 2018-08-28T15:31:41.000100Z

Do you have an example without reduce?

moo 2018-08-28T15:32:14.000100Z

because (reduce + all the args) would work, so … I missed the magic somehow

2018-08-28T15:32:20.000100Z

I might be able to come up with one, but let’s look at this one for just a minute

moo 2018-08-28T15:32:22.000100Z

(if that makes sense)

moo 2018-08-28T15:32:30.000100Z

k, I’m with ya

2018-08-28T15:32:42.000100Z

The trick here is that this reduce is actually happening inside the definition for +

2018-08-28T15:33:02.000200Z

also it’s reduce1 and not reduce

moo 2018-08-28T15:33:11.000200Z

oh

2018-08-28T15:33:37.000100Z

Yeah we’re digging into the innards of how Clojure works

2018-08-28T15:33:47.000100Z

we’re either brave or dumb 😆

moo 2018-08-28T15:33:55.000100Z

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)))

moo 2018-08-28T15:34:14.000200Z

brave and true right 😉

2018-08-28T15:34:25.000100Z

Yeah, that should work, but how does it work?

moo 2018-08-28T15:34:46.000100Z

great now I won’t wonder about built-in +, okay, thinking about it now

2018-08-28T15:36:22.000100Z

Ok, actually I’d say don’t look at that

moo 2018-08-28T15:37:01.000100Z

k skipping

2018-08-28T15:37:06.000100Z

It’s doing a bunch of extra stuff we don’t want to mess with for now

moo 2018-08-28T15:37:31.000100Z

oh reduce1 is eating one element

moo 2018-08-28T15:37:41.000100Z

like this:

2018-08-28T15:37:49.000100Z

Yeah, it kind of works that way

2018-08-28T15:38:01.000200Z

let’s step thru it with (+ 1 2 3 4)

moo 2018-08-28T15:38:17.000100Z

(reduce1 my-plus
(my-plus x y) ; combine two elements into one by calling the 2-arity)
more))) ; now more is one shorter?

moo 2018-08-28T15:38:44.000100Z

k let’s step

2018-08-28T15:39:04.000100Z

I’m going to use the built in source for + because it’s shorter to type 🙂

moo 2018-08-28T15:39:07.000100Z

so at the beginning, we are calling the [x y & more]

moo 2018-08-28T15:39:12.000100Z

k gotcha

2018-08-28T15:39:24.000100Z

Yup, and what values do x, y, & more have here?

moo 2018-08-28T15:39:26.000100Z

so thats (+ 1 2 [3 4])

moo 2018-08-28T15:39:34.000200Z

wait

moo 2018-08-28T15:39:47.000100Z

now that’s right

2018-08-28T15:40:12.000100Z

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]

moo 2018-08-28T15:40:50.000100Z

(reduce + (+ 1 2) [3 4]); (+ 1 2) ==> is 3 so that gives:

moo 2018-08-28T15:41:03.000100Z

(+ 3 3 [4])

moo 2018-08-28T15:41:23.000100Z

(+ 6 4)

moo 2018-08-28T15:41:26.000100Z

(+ 10)

moo 2018-08-28T15:41:27.000100Z

10

moo 2018-08-28T15:41:28.000200Z

?

moo 2018-08-28T15:41:38.000100Z

Did I unwind that right?

2018-08-28T15:42:04.000100Z

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.

moo 2018-08-28T15:42:35.000100Z

Oh man that was super helpful!

2018-08-28T15:42:46.000100Z

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.

moo 2018-08-28T15:43:07.000100Z

oh (+ 10) isn’t called

moo 2018-08-28T15:43:13.000100Z

right

moo 2018-08-28T15:43:23.000100Z

that’s a special case for one arg

2018-08-28T15:43:38.000100Z

Yeah, special cases for zero arity and 1-arity.

moo 2018-08-28T15:43:58.000100Z

Was it (+ 3 3 [4]) or (+ 3 3 4)?

moo 2018-08-28T15:44:28.000100Z

is more always a vector because of &?

2018-08-28T15:44:54.000100Z

The arguments to + will be the numbers without the vector — yes, the & is what makes more a vector

moo 2018-08-28T15:45:25.000100Z

yes, I can add. Thanks. Okay, I’ll go mess around in the repl until comp plops out. 🙂

2018-08-28T15:45:36.000100Z

I think reduce1 is converting it back to the (+ 3 3 4) form again somehow

2018-08-28T15:45:59.000100Z

but I didn’t follow the code all the way thru to understand exactly how

2018-08-28T15:46:20.000100Z

Alrighty, have fun and good luck 🙂

moo 2018-08-28T15:46:40.000100Z

So, to summarize what I understand

moo 2018-08-28T15:47:06.000100Z

Actually crap, I”m still missing something w.r.t. defn and parameters

moo 2018-08-28T15:47:28.000100Z

I see now how we can handle n-arity for actually doing something

moo 2018-08-28T15:47:56.000100Z

and I see how I could return f1(f2())

moo 2018-08-28T15:48:28.000100Z

but if we make g=f1 of f2, I don’t see how to call g(more)

2018-08-28T15:49:09.000100Z

Yeah, there’s a trick to thinking recursively

moo 2018-08-28T15:49:21.000100Z

k, … off to puzzlement 🙂

2018-08-28T15:58:13.000100Z

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.

moo 2018-08-28T15:59:49.000100Z

Yep that makes sense

moo 2018-08-28T16:00:18.000100Z

but, let’s take something non-recursive to show where I’m stuck, a comp function that only takes two functions

moo 2018-08-28T16:00:50.000100Z

(defn my-2comp [f1 f2] (f1 (f2)))

moo 2018-08-28T16:01:24.000200Z

do I need to handle the arguments to f2 in my-2comp for it to work?

moo 2018-08-28T16:03:32.000100Z

like, do I need to build a tail-recursion part to handle inputs to the final function or am I missing something else

2018-08-28T16:03:33.000100Z

Hmm, actually let me think about that for a minute

2018-08-28T16:04:57.000100Z

Ok, there’s one more piece you’re going to need, and that’s the opposite of & more.

moo 2018-08-28T16:06:24.000100Z

I thought maybe I needed to destructure last and second to last or something.

2018-08-28T16:06:44.000100Z

Actually, we’ve reached the point where the problem you’re working on is making me have to stop and think

moo 2018-08-28T16:06:45.000100Z

because it feels like I need to eat the tail not the head

moo 2018-08-28T16:07:34.000200Z

🙂

2018-08-28T16:08:08.000200Z

You’re on the right track, because you definitely need the right-most function to be the first one that actually executes

moo 2018-08-28T16:09:14.000100Z

I wish you could do this [[& first-fns f-second-to-last f-last]]

moo 2018-08-28T16:09:54.000100Z

like the opposite of [[f1 f2 & rest]]

moo 2018-08-28T16:10:12.000100Z

back to my-2comp at the repl… 🙂

moo 2018-08-28T16:11:35.000100Z

actually, I just don’t get what happens to the args of g for example

2018-08-28T16:11:59.000100Z

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.

moo 2018-08-28T16:11:59.000200Z

(defn my-2comp [f1 f2 & more] (f1 (f2 more))) is wrong because 2-comp should only take two functions

moo 2018-08-28T16:12:25.000200Z

maybe I should start with my-1comp

moo 2018-08-28T16:13:22.000100Z

(defn my-1comp [f] f) works

2018-08-28T16:13:40.000100Z

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

moo 2018-08-28T16:13:50.000100Z

(def test-fn (my-1comp -))
(test-fn 0 5)
;; --> -5

moo 2018-08-28T16:14:32.000200Z

oh, (defn my-1comp [f] f) vs (defn my-1comp [f] (f))

2018-08-28T16:14:42.000100Z

Your my-1comp is returning a function, which is correct. It’s not calling the function, it’s just returning it

2018-08-28T16:15:16.000100Z

so your first version of my-1comp is returning the correct type of result.

moo 2018-08-28T16:15:25.000100Z

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

2018-08-28T16:15:45.000100Z

right

2018-08-28T16:16:28.000100Z

my-1comp (first version) works because you only have one argument, so you can just return it.

moo 2018-08-28T16:17:08.000100Z

but this doesn’t work:

moo 2018-08-28T16:17:16.000100Z

(defn my-2comp 
  ([f] f)
  ([f1 f2] (f1 f2)))

moo 2018-08-28T16:17:29.000100Z

I don’t see why

2018-08-28T16:18:02.000300Z

That’s not working because it’s returning the result of calling f1 on f2.

moo 2018-08-28T16:18:17.000100Z

so I need to suspend evaluation?

2018-08-28T16:18:51.000100Z

Right, and the way you do that is by returning an anonymous function (fn [...] ...)

moo 2018-08-28T16:19:04.000100Z

I see

moo 2018-08-28T16:19:19.000100Z

that’s how to be lazy in this case

2018-08-28T16:20:07.000100Z

You could put it that way (but “lazy” usually refers to something slightly different in Clojure)

moo 2018-08-28T16:20:43.000100Z

that’s how I return a 1st class function instead of returning the immediate result of applying that fn

2018-08-28T16:20:58.000100Z

Exactly

moo 2018-08-28T16:21:38.000100Z

so it appears like I’ll need to overload that anon function for any arity

2018-08-28T16:22:15.000100Z

Correct (which is why I recommend starting off with only comp-ing single-arity functions)

moo 2018-08-28T16:22:29.000100Z

okay

2018-08-28T16:22:56.000100Z

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.

moo 2018-08-28T16:23:13.000100Z

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.

moo 2018-08-28T16:23:16.000100Z

yeah, build it up

moo 2018-08-28T16:23:18.000100Z

Thank you!!!

👍 1
moo 2018-08-28T16:34:14.000100Z

well, I found one way to solve my wrapping issue

moo 2018-08-28T16:34:26.000100Z

(defn my-1comp 
  ([f] 
      (fn 
        ([] f)
        ([a1] (f a1))
        ([a1 & rest] (apply f a1 rest)))))

moo 2018-08-28T16:35:41.000100Z

apply works and does the wrapping. I suppose I ’ought to implement apply after this to make sure I get that part too.

2018-08-28T16:46:38.000100Z

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)

2018-08-28T16:47:13.000100Z

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.

moo 2018-08-28T16:49:00.000100Z

it’s working somehow

moo 2018-08-28T16:49:15.000200Z

(however, I of course believe you)

moo 2018-08-28T16:49:35.000100Z

oh wait, no I didn’t test it correctly

moo 2018-08-28T16:50:30.000100Z

so it should be (f) for that zero-arity

2018-08-28T16:50:37.000100Z

right

2018-08-28T16:51:15.000100Z

The reason (f) was wrong before was because in the earlier versions it wasn’t inside a (fn [...] ...)

2018-08-28T16:52:04.000100Z

Now that my-1comp is returning an anonymous function, you need to have the anonymous function actually call it.

moo 2018-08-28T16:52:49.000100Z

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

moo 2018-08-28T16:53:10.000100Z

that makes sense

2018-08-28T16:55:47.000100Z

:thumbsup: Gotta sign off for a bit, I’ll check in later and see how its going

moo 2018-08-28T17:05:03.000100Z

Thanks!