Fork me on GitHub
#braveandtrue
<
2018-08-29
>
jm.moreau14:08:22

Hi @manutter51 good morning. 🙂

jm.moreau14:08:35

So, I figured out how to get the end of a seq for my comp

jm.moreau14:08:06

((fn [v] (let [[fn-k & rest] (rseq v)] (str fn-k))) ["a" "b" "c" "d"])
as a test works, and gives me the last fn however. I want to un-reverse that rest for the next recursive call and that isn’t working.
((fn [v] (let [[fn-k & rest] (rseq v)] (str (rseq rest)))) ["a" "b" "c" "d"])
     
; ==> ClassCastException clojure.lang.APersistentVector$RSeq cannot be cast to clojure.lang.Reversible  clojure.core/rseq (core.clj:1532)

jm.moreau14:08:54

I’m thinking along the lines of this (but it doesn’t work yet

(defn my-comp
  ([f1 f2] (fn
            ([] (f1 (f2)))
            ([a1] (f1 (f2 a1)))
            ([a1 & rest] (f1 (apply f2 a1 rest)))))
  ([f1 f2 & morefns] (
                       let [[fn-k & rest] (rseq morefns)] (fn-k (apply my-comp f1 f2 (rseq rest))))))

jm.moreau14:08:27

that last line should call (fn-k (apply my-comp ( reversed rest)))

manutter5114:08:26

Hmm, I hadn’t thought of trying to reverse the order of the arguments. You might be able to get that to work, but there is a simpler approach

jm.moreau14:08:27

it seems too complex. Okay, I’ll keep thinking about it

jm.moreau14:08:08

maybe I can nest functions with reduce…

jm.moreau14:08:31

like (reduce [bunch of funcs] args)

manutter5114:08:38

That’s close, but Clojure’s version of reduce is going to let you down because it works from left-to-right

manutter5114:08:02

Let’s try something even simpler: can you write a function nester that works like this: (nester 1 2 3 4 5 6) ;; ==> [1 [2 [3 [4 [5 [6]]]]]]?

manutter5114:08:25

(nester) ;; => []
(nester 1) ;; => [1]
(nester 1 2) ;; => [1 [2]]
;; etc...

manutter5115:08:21

Here’s a version that won’t work:

(defn bad-nester [& args]
   (reduce (fn [a i] [a i]) [] args))
=> #'user/bad-nester
(bad-nester 1 2 3 4 5 6)
=> [[[[[[[] 1] 2] 3] 4] 5] 6]
This is what I meant by “clojure’s reduce will let you down because it works from left to right.”

jm.moreau15:08:38

working on nester

jm.moreau15:08:50

man,.. not getting this right away is bugging me. 🙂

jm.moreau15:08:35

(defn nester 
      ([] [])
      ([x] [x])
      ([x & rest] (conj [x] (nester rest))))
(nester 1 2 3)
user=>
[1 [(2 3)]]

jm.moreau15:08:02

I’m not sure why this isn’t working,.. because that last line should eat one element and recurse

manutter5115:08:39

You’re on the right track, but rest is a seq because of the &, so when you call (nester rest) you’re actually calling (nester (2 3)), and that’s hitting your 1-arity version.

jm.moreau15:08:40

k so I need to convert to a vector to call the 2-arity

jm.moreau15:08:44

lemme go try.

manutter5115:08:26

No, it’s not seq vs vector, that’ll just give you (nester [2 3])

jm.moreau15:08:02

so… I need to explode it.

manutter5115:08:25

Whenever you’re running into (foo [x y z]) where you want (foo x y z), that’s when you need apply

jm.moreau15:08:04

(defn nester 
      ([] [])
      ([x] [x])
      ([x & rest] (conj [x] (apply nester rest))))
works!

jm.moreau15:08:32

okay, now I’m one step closer… back to it

manutter5115:08:42

also just as a stylistic/idiom suggestion, it’s common to have args that look like [x & xs] rather than [x & rest] because rest is a clojure keyword

jm.moreau15:08:53

xs for x-sequence?

manutter5115:08:25

“more than one x” 🙂

manutter5115:08:51

also [k & ks], [coll & colls], etc

jm.moreau15:08:48

So this doesn’t work because the only the inner most functions handles > 0 arity…

(defn comp-nester 
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & rest] (apply f a1 rest))))
      ([f & fs] #(f (apply comp-nester fs))))

jm.moreau15:08:20

so that means that last recursive line at the end needs to take any arity and apply…

manutter5115:08:06

The trick is that only the last arg needs to return a multi-arity function, and by the time you reach the last arg, you’re down to the single-arity version of your comp-nester

jm.moreau15:08:04

Getting closer:

(defn comp-nester 
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & an] (apply f a1 an))))
      ([f & fs] (fn 
                   ([] (f (apply comp-nester fs)))
                   ([a1] (f (apply comp-nester fs a1)))
                   ([a1 & an] (f (apply comp-nester fs a1 an))))))

jm.moreau15:08:25

don’t all the others needs to “pass” the context down?

jm.moreau15:08:39

oh, … yeah they don’t

jm.moreau15:08:37

because we’re doing (f1 (f2 (f-last many args)))

manutter5115:08:43

for your 0- and 1-arity versions, you don’t need apply btw

manutter5115:08:52

Yeah, you got it

jm.moreau15:08:27

so, why doesn’t this work then:

(defn comp-nester 
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & an] (apply f a1 an))))
      ([f & fs] (fn [] (f (apply comp-nester fs)))))

(def neg-quot (comp-nester - /))
user=>
#'user/neg-quot
(neg-quot 6 2)
ArityException Wrong number of args (2) passed to: user/comp-nester/fn--36155  clojure.lang.AFn.throwArity (AFn.java:429)

manutter5115:08:16

Ok, that’s close

manutter5115:08:12

On the last line, (fn [] ...) needs to take 1 argument, because it’s going to be handed the result of the previous fn

jm.moreau15:08:27

I thought so, but where do I put that arg?

jm.moreau15:08:54

because in (f1 (f2 (f-last many args))) f1-f-last-1 are all 1-arity

manutter5115:08:02

Heh, my turn to think again 🙂

jm.moreau15:08:43

I thought that in (f (apply comp-nester fs)), the inner most () is the 1-arity

jm.moreau15:08:40

This also doen’t work:

(defn comp-nester 
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & an] (apply f a1 an))))
      ([f & fs] (fn [a] (f (apply comp-nester fs a)))))

manutter5115:08:40

Aha, I’ve got a clue

jm.moreau15:08:49

(the above doesn’t make sense because in that case I’m giving args to compnester which should only take fns)

peterwestmacott07:08:18

Does the -inf function defined with Float/NEGATIVE_INFINITY work for doubles? (it looks a bit odd, but I genuinely don’t know)

manutter5115:08:10

(I cheated and looked at the source for comp, which is all low level stuff and hard to read, but it told me what I needed, I think)

manutter5115:08:54

So here’s the trick: if you have a list, i.e. something inside parens, Clojure assumes by default that the first item in that list is a function. In other words if you give Clojure a list that contains a function and some arguments, it’ll try and run it.

manutter5115:08:40

Oh, hrm, my nifty example isn’t working

jm.moreau15:08:12

actually… regarding arity

jm.moreau15:08:02

if f_k takes (x,y,z) and g=f1(f_k(x,yz), then g takes (x,y,z)

jm.moreau15:08:14

so… I do need to pass all the args all the way down

jm.moreau15:08:22

I keep thinking in other languages… 🙂

jm.moreau15:08:12

well, to figure this part out, I”m going back to my-2comp that only composes two functions

jm.moreau15:08:59

(defn my-2comp
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & an] (apply f a1 an))))
      ([f f2] (fn ([] (f (f2)))        
                ([a] (f (f2 a)))   
                ([a & as] (f (apply f2 a as))))))

jm.moreau15:08:14

So we do need to pass the arguments down to the inner most function

jm.moreau15:08:39

so I’ll try to recurse on 3-functions

jm.moreau16:08:36

it seems like the problem is here:

jm.moreau16:08:35

([f f2 & fns] (fn ([] (f (f2))))        
                    ([a] (f (f2 a)))   
                    ([a & as] (f (f2 (apply my-multi-comp fns a as))))))
on the last line how do I pass the arguments so they they go to the last function and as additional arguments (functions) to my-multi-comp?

jm.moreau16:08:22

this is why I thought I had to reverse the sequence of functions

manutter5116:08:31

Ok, it took me a minute but I think I figured it out

manutter5116:08:39

actually let me try one more test…

manutter5116:08:12

Yeah looks good

manutter5116:08:41

Now, how to explain the path from where you are to what my answer looks like 🤔

jm.moreau16:08:26

:rolling_on_the_floor_laughing:

jm.moreau16:08:39

did my ramblings make sense above?

jm.moreau16:08:53

regarding passing parameters down the line?

manutter5116:08:36

Yeah, and in fact I ended up passing & args thru on my anonymous functions too

manutter5116:08:36

Part of the problem is that we’re writing a function that returns a function, so when we say “args” it’s ambiguous which args we’re referring to.

manutter5116:08:52

I’m going to say “comp-args” versus “anon-args”

manutter5116:08:15

(defn my-comp [comp-args] (fn [anon-args] ... ))

jm.moreau16:08:15

okay so we do need to pass the args, I thought so

manutter5116:08:26

Yup, you were right

manutter5116:08:43

apologies for leading you astray

jm.moreau16:08:06

ha, don’t apologize! You’re teaching me clojure!

manutter5116:08:18

and having a blast too 😄

manutter5116:08:00

Ok, so let’s look back at defining our own comp. There’s actually 3 “simple” cases:

manutter5116:08:57

(comp) ;; ==> returns identity
(comp f) ;; ==> returns f
(comp f g) ;; ==> returns (fn [& args] (f (apply g args)))

jm.moreau16:08:17

I didn’t think of case #1

manutter5116:08:42

Yeah, that’s a clojurism: “try to make zero-arity calls return something sensible”

jm.moreau16:08:29

does [& args] cover all arities?

manutter5116:08:22

Essentially. The combination of & args and (apply f args) works the same as if you had just called f with whatever args.

jm.moreau16:08:44

cool so this:

jm.moreau16:08:52

(defn my-2comp
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & an] (apply f a1 an))))
      ([f f2] (fn ([] (f (f2)))        
                ([a] (f (f2 a)))   
                ([a & as] (f (apply f2 a as))))))
could have been:

manutter5116:08:52

so your anonymous function doesn’t need to count anon-args

jm.moreau16:08:43

(defn my-2comp
      ([f] (fn 
            ([& an] (apply f an))))
      ([f f2] (fn  [& as] (f (apply f2 as))))))

jm.moreau16:08:50

(with the correct parens…)

manutter5116:08:19

Yes, exactly

jm.moreau16:08:12

so, how to recurse and deal with that…?

jm.moreau16:08:34

Dude, this problem gets a little † for hard.

manutter5116:08:40

Ok, so our story so far…

(defn my-comp
  ([] identity)
  ([f] f)
  ([f g]
   (fn [& args]
     (f (apply g args))))
  ([f g & more]
    ;; hmmm... ?
    ))

manutter5116:08:42

We have f, g, and more, and we want to recursively call my-comp with a shorter list of comp-args.

jm.moreau16:08:31

yes, and jumping ahead I imagine once we’ve worked up to [f g & more] it’ll be clear how to recurse at just two functions

jm.moreau16:08:46

but, yes, I’m with you so far

jm.moreau16:08:05

I was here:

([f f2 & fns] (fn ([] (f (f2))))        
                    ([a] (f (f2 a)))   
                    ([a & as] (f (f2 (apply my-multi-comp fns a as))))))

jm.moreau16:08:10

which is wrong

manutter5116:08:20

So given (my-comp f g h i j) we want to end up with a function something like this:

(fn [& args]
    (f (g (h (i (apply j args))))))

jm.moreau16:08:02

I’m slightly frustrated that it hasn

jm.moreau16:08:29

’t popped out at me yet. Because this sound like an if for the end case and nester

jm.moreau16:08:38

anyways, back to listening

manutter5116:08:39

So notice that this bit (i (apply j args)) is what we get from our 2-arity version of my-comp

manutter5116:08:39

So let’s substitute:

(fn [& args]
  (f (g (h (my-comp i j)))))

manutter5116:08:06

(Hmm, am I doing that right?)

manutter5116:08:46

Whoops, no, it’s this:

(fn [& args]
  (f (g (h (apply (my-comp i j) args)))))

jm.moreau16:08:13

yes, this is making sense

jm.moreau16:08:08

I didn’t see where the args would go and how’d they’d get passed, but this looks right

jm.moreau16:08:24

args go to the result of the final two-arity call

manutter5116:08:25

Ok, now we’ve got the same pattern again: (h (apply (my-comp i j) args)) matches what we get from our 2-arity version of my-comp

manutter5116:08:05

apart from the brain cramp you get from manually unrolling a recursion, lol

manutter5116:08:37

Tell you what, let’s use a let to make it a bit easier to read

manutter5116:08:14

(fn [& args]
  (let [ij (my-comp i j)]
    (f (g (h (apply ij args))))))

jm.moreau16:08:56

oh,… so close

jm.moreau16:08:18

antici… … … pation.

jm.moreau16:08:47

let does make that clearer

manutter5116:08:48

I’m calling my-comp recursively, and making the list shorter

jm.moreau16:08:30

can you go out one level of parens?

manutter5116:08:36

But I’m doing it manually

jm.moreau16:08:52

([f g &h]

manutter5116:08:47

(make sure it’s & h not &h)

manutter5116:08:34

Hmm, my brain is overheating trying to figure out how to explain the leap from manually combining i and j into the general recursive case

jm.moreau16:08:47

I’m having trouble seeing how it unwinds

jm.moreau16:08:07

sometimes, you can see okay we unwind to the special case, then special case is like so

jm.moreau16:08:14

feels hard today.

manutter5116:08:19

Yeah, it’s something I’ve learned to do without thinking, which means it’s really hard to explain the thinking

jm.moreau16:08:21

but, the special case it those last two

manutter5116:08:41

Ok, let’s look at just the h arg and following, before and after

jm.moreau16:08:15

I feel like I should imagine this

jm.moreau16:08:55

(f1 (unprocessed stuff)) (f1 (f2 (unproccessed stuff))) (f1 (f2 (fn args))))

jm.moreau16:08:17

and thinking this way

jm.moreau16:08:30

the two --> one list eating step is

jm.moreau16:08:52

(f1_f2 (recurse here))

jm.moreau16:08:16

that’ll work with “eating the head”

manutter5116:08:46

Ok, check this out:

(fn [& args]
  (f (g (h (i (apply j args))))))
           ^^^^^^^^^^^^^^^^^

(fn [& args]
  (let [ij (my-comp i j)]
        ^^^^^^^^^^^^^^^^
    (f (g (h (apply ij args))))))
             ^^^^^^^^^^^^^^^

jm.moreau16:08:11

so I”m here

(defn comp-nester 
      ([f] (fn 
            ([& args] (apply f args))))
      ([f g] (fn ([& args] (f (apply g args)))))
      ([f g & h] (fn ([& args 
                        ;; so something to make the list shorter here... 
                       ;; use let to make fg
                       (fg (comp-nester h))])))) ; where do the args go?

jm.moreau16:08:18

looking at yours…

manutter5116:08:24

We’re working our way from right-to-left, because that’s what we need to do to compose functions

manutter5116:08:26

The last 3 comp args are h, i, and j, and we make that list shorter by combining the last 2 args via the recursive call to (my-comp i j)

manutter5116:08:42

then we bring the args back in with apply, so instead of (h (i (apply j args))), we have (h (apply ij args))

manutter5116:08:42

(h (i (apply j args)))
(h (apply ij args))

manutter5116:08:25

so it’s “eating” the last 2 comp args, but keeping the general shape of (apply ___ args) at the end

jm.moreau16:08:06

oh, was I thinking left to right?

manutter5116:08:36

probably. It’s the right-to-left bit that’s tricky about this problem

jm.moreau16:08:07

so the args get passed because we recursively use apply

manutter5116:08:30

Each time you recurse, you want the (apply ___ args) to absorb the next arg to its left.

jm.moreau16:08:48

okay, so I’m not 100% there

jm.moreau16:08:56

this is why I was looking at rseq

manutter5116:08:34

Yeah, rseq will make the problem easier, but if you can figure it out without rseq you’ll have a much more powerful understanding of recursion.

jm.moreau16:08:34

in the nester case, it was just (x (recurse the rest))

jm.moreau16:08:54

okay, thanks so much !

jm.moreau16:08:19

I’m out of clojure minutes for the day. I’ll puzzle it out a little more tomorrow and see what I can’t figure with all the hints

manutter5116:08:35

Sounds good, have fun and good luck

jm.moreau17:08:09

I couldn’t help myself. I got it! But I came away with questions. my-comp1 works, and my-comp3 doesn’t and it’s not clear to me why. Am I not “eating” in version 3?

(defn my-comp1
      ([] identity)
      ([f] (fn 
            ([& args] (apply f args))))
      ([f g] (fn ([& args] (f (apply g args)))))
      ([f g & h] (fn [& args]
                     (let [h_rest (apply my-comp h)]
                          (f (g (apply h_rest args)))))))
(defn my-comp2
      ([] identity)
      ([f] (fn 
            ([& args] (apply f args))))
      ([f g] (fn ([& args] (f (apply g args)))))
      ([f g & h] (fn [& args]
                    (f (g (apply (apply my-comp h) args))))))                                    
(defn my-comp3
      ([] identity)
      ([f] (fn 
            ([& args] (apply f args))))
      ([f & g] (fn [& args]
                 (let [g_rest (apply my-comp g)]
                      (f (apply g_rest args))))))

manutter5117:08:41

First a quick comment: returning (fn [& args) (apply f args)) is the same as just returning f, so I’d just return f in that case.

manutter5117:08:20

Actually, your my-comp3 seems to be working for me, except not quite how I would expect

manutter5117:08:55

I added some diagnostic code to it and ran it, check it out:

(defn my-comp3
  ([] identity)
  ([f] (prn :what :composing-arity-1 :f f)
    f)
  ([f & g]
   (prn {:what :composing-more :f f :g g})
   (fn [& args]
     (let [g_rest (apply my-comp3 g)]
       (prn {:what :calling :args args :f f :g g :g_rest g_rest})
       (f (apply g_rest args))))))
=> #'user/my-comp3
(def double-inc-neg-quot (my-comp3 inc inc - /))
{:what :composing-more, :f #function[clojure.core/inc], :g (#function[clojure.core/inc] #function[clojure.core/-] #function[clojure.core//])}
=> #'user/double-inc-neg-quot
(double-inc-neg-quot 6 2)
{:what :composing-more, :f #function[clojure.core/inc], :g (#function[clojure.core/-] #function[clojure.core//])}
{:what :calling, :args (6 2), :f #function[clojure.core/inc], :g (#function[clojure.core/inc] #function[clojure.core/-] #function[clojure.core//]), :g_rest #function[user/my-comp3/fn--42232]}
{:what :composing-more, :f #function[clojure.core/-], :g (#function[clojure.core//])}
{:what :calling, :args (6 2), :f #function[clojure.core/inc], :g (#function[clojure.core/-] #function[clojure.core//]), :g_rest #function[user/my-comp3/fn--42232]}
:what :composing-arity-1 :f #function[clojure.core//]
{:what :calling, :args (6 2), :f #function[clojure.core/-], :g (#function[clojure.core//]), :g_rest #function[clojure.core//]}
=> -1

manutter5117:08:22

gotta go, tho, it’s meeting time

manutter5120:08:49

Oh, right, the difference between your code and mine is that your let is inside the anonymous function, but in my code the anonymous function is inside the let.

manutter5120:08:29

So basically your my-comp3, with the anon-fn inside the let, would look like this:

(defn my-comp4
  ([] identity)
  ([f] f)
  ([f & g]
    (let [g2 (apply my-comp4 g)]
      (fn [& args]
        (f (apply g2 args))))))

manutter5120:08:11

That should produce the same result as my-comp3 except that my-comp4 will do all the composing up front, one time, where my-comp3 does most of the composing at run-time (i.e. when you run the result)

jm.moreau20:08:05

I see… Thank you!

jm.moreau20:08:44

is there a handy way to save the slack history? or just copy and paste?

manutter5120:08:20

Have to copy-n-paste, this is the free version of slack, so the history expires/disappears eventually

jm.moreau20:08:47

Man, thanks again for the help!