Fork me on GitHub
#braveandtrue
<
2018-08-28
>
moo01:08:34

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

moo01:08:37

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

manutter5102:08:09

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.

manutter5102:08:32

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

manutter5102:08:35

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.

manutter5102:08:24

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
  )

manutter5102:08:18

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

manutter5102:08:08

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

manutter5102:08:32

So any number of additional arguments will fit in z.

manutter5102:08:46

The array just gets bigger the more args you add.

manutter5102:08:29

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.

manutter5102:08:59

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

moo14:08:36

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.

moo14:08:39

for example

moo14:08:55

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

manutter5114:08:59

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.

manutter5114:08:17

The comp fn itself accepts only fns as arguments.

moo14:08:48

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

manutter5114:08:06

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

moo14:08:16

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

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

moo14:08:39

that was getting me

manutter5114:08:48

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

moo14:08:21

actually I’m still confused because

manutter5114:08:21

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

manutter5114:08:40

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

moo14:08:51

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

manutter5114:08:52

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

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

manutter5114:08:53

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)

moo14:08:18

maybe I’m confused about def vs defn

manutter5114:08:49

How comfortable are you with anonymous functions?

moo14:08:58

can I use defn to make an inc function factory?

moo14:08:54

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

manutter5115:08:10

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

manutter5115:08:10

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)

moo15:08:36

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

manutter5115:08:13

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

manutter5115:08:47

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

moo15:08:22

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

moo15:08:06

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

manutter5115:08:47

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.

moo15:08:43

that part I understand

moo15:08:53

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

moo15:08:39

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

moo15:08:05

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

moo15:08:27

but maybe it does?

moo15:08:29

I’m clearly missing something basic

manutter5115:08:38

Sorry, got called away for a bit

manutter5115:08:25

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

manutter5115:08:03

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

manutter5115:08:22

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

manutter5115:08:05

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

moo15:08:41

Do you have an example without reduce?

moo15:08:14

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

manutter5115:08:20

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

moo15:08:22

(if that makes sense)

moo15:08:30

k, I’m with ya

manutter5115:08:42

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

manutter5115:08:02

also it’s reduce1 and not reduce

manutter5115:08:37

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

manutter5115:08:47

we’re either brave or dumb 😆

moo15:08:55

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

moo15:08:14

brave and true right 😉

manutter5115:08:25

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

moo15:08:46

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

manutter5115:08:22

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

moo15:08:01

k skipping

manutter5115:08:06

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

moo15:08:31

oh reduce1 is eating one element

moo15:08:41

like this:

manutter5115:08:49

Yeah, it kind of works that way

manutter5115:08:01

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

moo15:08:17

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

moo15:08:44

k let’s step

manutter5115:08:04

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

moo15:08:07

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

moo15:08:12

k gotcha

manutter5115:08:24

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

moo15:08:26

so thats (+ 1 2 [3 4])

moo15:08:47

now that’s right

manutter5115:08:12

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]

moo15:08:50

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

moo15:08:03

(+ 3 3 [4])

moo15:08:38

Did I unwind that right?

manutter5115:08:04

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.

moo15:08:35

Oh man that was super helpful!

manutter5115:08:46

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.

moo15:08:07

oh (+ 10) isn’t called

moo15:08:23

that’s a special case for one arg

manutter5115:08:38

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

moo15:08:58

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

moo15:08:28

is more always a vector because of &?

manutter5115:08:54

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

moo15:08:25

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

manutter5115:08:36

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

manutter5115:08:59

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

conan15:08:27

never seen anything unusual, give it a function and it just calls it. It does pass in the on-click event, so maybe add that in as an arg to your handler function?

manutter5115:08:20

Alrighty, have fun and good luck 🙂

moo15:08:40

So, to summarize what I understand

moo15:08:06

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

moo15:08:28

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

moo15:08:56

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

moo15:08:28

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

manutter5115:08:09

Yeah, there’s a trick to thinking recursively

moo15:08:21

k, … off to puzzlement 🙂

manutter5115:08:13

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.

moo15:08:49

Yep that makes sense

moo16:08:18

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

moo16:08:50

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

moo16:08:24

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

moo16:08:32

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

manutter5116:08:33

Hmm, actually let me think about that for a minute

manutter5116:08:57

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

moo16:08:24

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

manutter5116:08:44

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

moo16:08:45

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

manutter5116:08:08

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

moo16:08:14

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

moo16:08:54

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

moo16:08:12

back to my-2comp at the repl… 🙂

moo16:08:35

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

manutter5116:08:59

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.

moo16:08:59

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

moo16:08:25

maybe I should start with my-1comp

moo16:08:22

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

manutter5116:08:40

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

moo16:08:50

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

moo16:08:32

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

manutter5116:08:42

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

manutter5116:08:16

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

moo16:08:25

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

manutter5116:08:28

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

moo16:08:08

but this doesn’t work:

moo16:08:16

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

moo16:08:29

I don’t see why

manutter5116:08:02

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

moo16:08:17

so I need to suspend evaluation?

manutter5116:08:51

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

moo16:08:19

that’s how to be lazy in this case

manutter5116:08:07

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

moo16:08:43

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

moo16:08:38

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

manutter5116:08:15

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

manutter5116:08:56

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.

moo16:08:13

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.

moo16:08:16

yeah, build it up

moo16:08:18

Thank you!!!

👍 1
moo16:08:14

well, I found one way to solve my wrapping issue

moo16:08:26

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

moo16:08:41

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

manutter5116:08:38

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)

manutter5116:08:13

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.

moo16:08:00

it’s working somehow

moo16:08:15

(however, I of course believe you)

moo16:08:35

oh wait, no I didn’t test it correctly

moo16:08:30

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

manutter5116:08:15

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

manutter5116:08:04

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

moo16:08:49

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

moo16:08:10

that makes sense

manutter5116:08:47

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