This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-08-29
Channels
- # aws (16)
- # beginners (14)
- # boulder-clojurians (1)
- # braveandtrue (184)
- # cider (9)
- # cljs-dev (17)
- # cljsrn (10)
- # clojure (70)
- # clojure-austin (16)
- # clojure-finland (2)
- # clojure-germany (4)
- # clojure-italy (6)
- # clojure-nl (6)
- # clojure-sanfrancisco (2)
- # clojure-spec (5)
- # clojure-uk (60)
- # clojurescript (82)
- # cursive (1)
- # data-science (1)
- # datomic (13)
- # defnpodcast (1)
- # docker (82)
- # figwheel-main (18)
- # fulcro (51)
- # funcool (6)
- # jobs (13)
- # jobs-discuss (5)
- # jobs-rus (7)
- # juxt (2)
- # leiningen (4)
- # lumo (5)
- # mount (1)
- # off-topic (38)
- # pedestal (9)
- # re-frame (13)
- # reagent (16)
- # shadow-cljs (70)
- # spacemacs (5)
- # specter (9)
- # sql (8)
- # tools-deps (2)
- # vim (6)
Hi @manutter51 good morning. 🙂
((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))))))
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
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.”(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.
No, it’s not seq vs vector, that’ll just give you (nester [2 3])
Whenever you’re running into (foo [x y z])
where you want (foo x y z)
, that’s when you need apply
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
“more than one x” 🙂
also [k & ks]
, [coll & colls]
, etc
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))))
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))))))
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
Heh, my turn to think again 🙂
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)
Does the -inf
function defined with Float/NEGATIVE_INFINITY
work for doubles? (it looks a bit odd, but I genuinely don’t know)
(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
well, to figure this part out, I”m going back to my-2comp that only composes two functions
(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))))))
([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?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:
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] ... ))
Yup, you were right
apologies for leading you astray
and having a blast too 😄
Ok, so let’s look back at defining our own comp
. There’s actually 3 “simple” cases:
(comp) ;; ==> returns identity
(comp f) ;; ==> returns f
(comp f g) ;; ==> returns (fn [& args] (f (apply g args)))
Yeah, that’s a clojurism: “try to make zero-arity calls return something sensible”
Essentially. The combination of & args
and (apply f args)
works the same as if you had just called f
with whatever args.
(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
Yes, exactly
Ok, so our story so far…
(defn my-comp
([] identity)
([f] f)
([f g]
(fn [& args]
(f (apply g args))))
([f g & more]
;; hmmm... ?
))
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
I was here:
([f f2 & fns] (fn ([] (f (f2))))
([a] (f (f2 a)))
([a & as] (f (f2 (apply my-multi-comp fns a as))))))
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))))))
So notice that this bit (i (apply j args))
is what we get from our 2-arity version of my-comp
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)))))
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))))))
I’m calling my-comp
recursively, and making the list shorter
But I’m doing it manually
(make sure it’s & h
not &h
)
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
Yeah, it’s something I’ve learned to do without thinking, which means it’s really hard to explain the thinking
Ok, let’s look at just the h
arg and following, before and after
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?
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
probably. It’s the right-to-left bit that’s tricky about this problem
right
Each time you recurse, you want the (apply ___ args)
to absorb the next arg to its left.
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.
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)
Have to copy-n-paste, this is the free version of slack, so the history expires/disappears eventually
My pleasure