Hi @manutter51 good morning. 🙂
So, I figured out how to get the end of a seq for my comp
but…
((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)
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))))))
that last line should call (fn-k (apply my-comp ( reversed rest)))
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
it seems too complex. Okay, I’ll keep thinking about it
maybe I can nest functions with reduce…
like (reduce [bunch of funcs] args)
That’s close, but Clojure’s version of reduce is going to let you down because it works from left-to-right
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]]]]]]
?
(nester) ;; => []
(nester 1) ;; => [1]
(nester 1 2) ;; => [1 [2]]
;; etc...
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.”working on nester
man,.. not getting this right away is bugging me. 🙂
(defn nester
([] [])
([x] [x])
([x & rest] (conj [x] (nester rest))))
(nester 1 2 3)
user=>
[1 [(2 3)]]
I’m not sure why this isn’t working,.. because that last line should eat one element and recurse
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.
k so I need to convert to a vector to call the 2-arity
lemme go try.
No, it’s not seq vs vector, that’ll just give you (nester [2 3])
so… I need to explode it.
Whenever you’re running into (foo [x y z])
where you want (foo x y z)
, that’s when you need apply
thanks!
(defn nester
([] [])
([x] [x])
([x & rest] (conj [x] (apply nester rest))))
works!okay, now I’m one step closer… back to it
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
thanks
xs for x-sequence?
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))))
so that means that last recursive line at the end needs to take any arity and apply…
“more than one x” 🙂
also [k & ks]
, [coll & colls]
, etc
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
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))))))
oh
don’t all the others needs to “pass” the context down?
oh, … yeah they don’t
because we’re doing (f1 (f2 (f-last many args)))
for your 0- and 1-arity versions, you don’t need apply
btw
Yeah, you got it
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)
Ok, that’s close
On the last line, (fn [] ...)
needs to take 1 argument, because it’s going to be handed the result of the previous fn
I thought so, but where do I put that arg?
because in (f1 (f2 (f-last many args))) f1-f-last-1 are all 1-arity
Heh, my turn to think again 🙂
I thought that in (f (apply comp-nester fs))
, the inner most () is the 1-arity
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)))))
Aha, I’ve got a clue
(the above doesn’t make sense because in that case I’m giving args to compnester which should only take fns)
yes!
(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)
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.
Oh, hrm, my nifty example isn’t working
actually… regarding arity
if f_k takes (x,y,z) and g=f1(f_k(x,yz), then g takes (x,y,z)
so… I do need to pass all the args all the way down
er,… no
ugh
I keep thinking in other languages… 🙂
well, to figure this part out, I”m going back to my-2comp that only composes two functions
this works:
(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))))))
So we do need to pass the arguments down to the inner most function
(it seems)
so I’ll try to recurse on 3-functions
it seems like the problem is here:
([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?this is why I thought I had to reverse the sequence of functions
Ok, it took me a minute but I think I figured it out
actually let me try one more test…
Yeah looks good
Now, how to explain the path from where you are to what my answer looks like :thinking_face:
:rolling_on_the_floor_laughing:
did my ramblings make sense above?
regarding passing parameters down the line?
Yeah, and in fact I ended up passing & args
thru on my anonymous functions too
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.
I’m going to say “comp-args” versus “anon-args”
(defn my-comp [comp-args] (fn [anon-args] ... ))
okay so we do need to pass the args, I thought so
Yup, you were right
apologies for leading you astray
ha, don’t apologize! You’re teaching me clojure!
and having a blast too 😄
Ok, so let’s look back at defining our own comp
. There’s actually 3 “simple” cases:
k
(comp) ;; ==> returns identity
(comp f) ;; ==> returns f
(comp f g) ;; ==> returns (fn [& args] (f (apply g args)))
yes
I didn’t think of case #1
Yeah, that’s a clojurism: “try to make zero-arity calls return something sensible”
does [& args]
cover all arities?
Essentially. The combination of & args
and (apply f args)
works the same as if you had just called f
with whatever args.
cool so this:
(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:so your anonymous function doesn’t need to count anon-args
(defn my-2comp
([f] (fn
([& an] (apply f an))))
([f f2] (fn [& as] (f (apply f2 as))))))
(with the correct parens…)
Yes, exactly
so, how to recurse and deal with that…?
Dude, this problem gets a little † for hard.
Ok, so our story so far…
(defn my-comp
([] identity)
([f] f)
([f g]
(fn [& args]
(f (apply g args))))
([f g & more]
;; hmmm... ?
))
yep
We have f
, g
, and more
, and we want to recursively call my-comp
with a shorter list of comp-args.
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
but, yes, I’m with you so far
I was here:
([f f2 & fns] (fn ([] (f (f2))))
([a] (f (f2 a)))
([a & as] (f (f2 (apply my-multi-comp fns a as))))))
which is wrong
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))))))
yes
I’m slightly frustrated that it hasn
’t popped out at me yet. Because this sound like an if for the end case and nester
anyways, back to listening
So notice that this bit (i (apply j args))
is what we get from our 2-arity version of my-comp
yes
So let’s substitute:
(fn [& args]
(f (g (h (my-comp i j)))))
(Hmm, am I doing that right?)
Whoops, no, it’s this:
(fn [& args]
(f (g (h (apply (my-comp i j) args)))))
yes, this is making sense
I didn’t see where the args would go and how’d they’d get passed, but this looks right
args go to the result of the final two-arity call
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
apart from the brain cramp you get from manually unrolling a recursion, lol
Tell you what, let’s use a let
to make it a bit easier to read
(fn [& args]
(let [ij (my-comp i j)]
(f (g (h (apply ij args))))))
oh,… so close
antici… … … pation.
let does make that clearer
I’m calling my-comp
recursively, and making the list shorter
can you go out one level of parens?
But I’m doing it manually
([f g &h]
…
(make sure it’s & h
not &h
)
k
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
I’m having trouble seeing how it unwinds
sometimes, you can see okay we unwind to the special case, then special case is like so
feels hard today.
Yeah, it’s something I’ve learned to do without thinking, which means it’s really hard to explain the thinking
but, the special case it those last two
Ok, let’s look at just the h
arg and following, before and after
I feel like I should imagine this
(f1 (unprocessed stuff)) (f1 (f2 (unproccessed stuff))) (f1 (f2 (fn args))))
and thinking this way
the two --> one list eating step is
(f1_f2 (recurse here))
that’ll work with “eating the head”
so….
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))))))
^^^^^^^^^^^^^^^
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?
looking at yours…
We’re working our way from right-to-left, because that’s what we need to do to compose functions
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)
then we bring the args back in with apply
, so instead of (h (i (apply j args)))
, we have (h (apply ij args))
(h (i (apply j args)))
(h (apply ij args))
so it’s “eating” the last 2 comp args, but keeping the general shape of (apply ___ args)
at the end
oh, was I thinking left to right?
probably. It’s the right-to-left bit that’s tricky about this problem
so the args get passed because we recursively use apply
right
Each time you recurse, you want the (apply ___ args)
to absorb the next arg to its left.
okay, so I’m not 100% there
this is why I was looking at rseq
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.
in the nester
case, it was just (x (recurse the rest))
okay, thanks so much !
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
Sounds good, have fun and good luck
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))))))
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.
Actually, your my-comp3
seems to be working for me, except not quite how I would expect
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
gotta go, tho, it’s meeting time
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
.
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))))))
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)
I see… Thank you!
is there a handy way to save the slack history? or just copy and paste?
Have to copy-n-paste, this is the free version of slack, so the history expires/disappears eventually
gotcha
Man, thanks again for the help!
My pleasure