isn't there a funcall
function in clojure? I know it's not absolutely necessary, but sometimes it can make the code more readable.
For example, this call to reduce
returns a function that needs to be called with no arguments.
(reduce (fn
([]
default-f)
([_ [keys f]]
(if (exists [x keys] (test value x))
(reduced f)
default-f)))
default-f
pairs)
It works if I just wrap this in a second set of parens.
((reduce (fn
([]
default-f)
([_ [keys f]]
(if (exists [x keys] (test value x))
(reduced f)
default-f)))
default-f
pairs))
But I think it is more readable if I use funcall to emphasize what's happening.
(funcall (reduce (fn
([]
default-f)
([_ [keys f]]
(if (exists [x keys] (test value x))
(reduced f)
default-f)))
default-f
pairs))
The infinite loop is that I meant to use (loop [pairs pairs] ...)
but instead i used (loop [pair pairs] ...)
... That's when I said to myself, what I really want to do is iterate over a sequence until some condition, which is exactly what reduce
/`reduced` is for.
but the conversion to reduce
didn't aid in readability, in my opinion for two reasons. 1. in order to prevent the default-f
function from being called unnecessarily was tricky, and 2. handling the empty sequence, reduce calls the given function with 0 arguments in case of an empty sequence, so I had to provide a multiple-arity function, which further obfuscates the code.
In the end, I think that loop
/`recur` is probably clearer.
w.r.t. the following suggestion
(loop [[pair & more] pairs]
(if pair
(do ... (recur more))
...))
I really don't like that coding style, even though I know it is very often used in the clojure community. One reason I don't like it is that it works accidentally. The code would fail if there is every a pair such as (false not-false)
in the list of pairs
.
I avoid this accidental dependence in my code when I can help it, even if that makes for someone non-ideomatic clojure coding style.(loop [[pair & more] [ [false true] ]]
(if pair
(do (println pair) (recur more))
nil))
[false true]
=> nil
A pair of false and true is still truthy. Admittedly the code doesn't hit everything if your list of pairs can contain nils.
Either way I would prefer reduce to loop when it's feasible, I was just trying to help point out where the infinite loop came in and what the form of loop
bindings is 🤷Maybe this would work for you
(defn -casep-helper [test value default-f & pairs]
@(reduce
(fn [agg [keys f]]
(if (exists [x keys] (test value x))
(reduced (delay (f)))
agg))
(delay (default-f))
pairs))
about the @, that's an interesting thought to avoid having to do a funcall.
@dstephens, you're right about a pair of [false non-false]
still being truthy. I guess I was thinking about adding the pair destructuring also. such as
(loop [[[key value] & pairs] pairs]
(if key ...
I could try something like this:
(loop [[[key value :as pair] & pairs] pairs]
(if pair ...
But I must admit that bothers me because for me, it still works accidentally.
OTOH, i'm, not completely comfortable using :as
in because it works sometimes but not others, and I don't remember the cases where it's legal.
For example, I know the following is illegal.
(defn foo [a & as :as all]
...)
and this is illegal as well
(defmacro foo [a & as :as all]
...)
(defn foo [& [a & as :as all]]
(println a)
(println as)
(println all))
=> #'user/foo
(foo 1 2 3 4)
1
(2 3 4)
(1 2 3 4)
=> nil
you have to have :as inside a destructure form I believe, so this worksotherwise :as
couldn't know whether to refer to a and as or just as or nothing
I'm not completely sure what you mean when you say it works accidentally, if you can't rely on pairs being a list of pairs then I'd suggest either the call point is wrong or you need another function that does validation first.
I guess the following isn't so bad.
(apply (reduce (fn
([]
default-f)
([_ [keys f]]
(if (exists [x keys] (test value x))
(reduced f)
default-f)))
default-f
pairs)
())
try .invoke
(.invoke (reduce
(fn [f k]
(fn []
(str (f) (k))))
[(fn [] "q") (fn [] "w") (fn [] "e")]))
Clojure is a Lisp-1, not a Lisp-2 like Common Lisp, so you can use functions in call position, or even any other expression that, when evaluated, returns a function, e.g.
user=> ((constantly true) 5)
true
In this, Clojure is more like Scheme than Common Lisp. I don't recall whether Scheme has funcall
, but I would guess no.
https://en.wikipedia.org/wiki/Common_Lisp#The_function_namespace
Another way to emphasize this is to use let
to bind the return value of the (reduce ...)
expression to some name, then in the body of the let
call that function, e.g.:
(let [f (reduce …)]
(f))
@andy.fingerhut yes that's a good idea as well. I ended up just implementing a funcall
function in my local utils library. Then after that I ended up removing the code altogehter, I think it is a bit clearer using loop/recur,
(defn -casep-helper [test value default-f & pairs]
(loop [pairs pairs]
(cond (empty? pairs)
(default-f)
:else
(let [[keys f] (first pairs)]
(if (exists [x keys] (test value x))
(f)
(recur (rest pairs)))))))
@andy.fingerhut, yes as i mentioned earlier, you're right. I can just wrap an extra set of parens around it and it works perfectly well. I just find that the explicit funcall
makes it easier to understand what's happening.
I originally had an error in this function and it made an infinite loop. Can you spot the error?
(defn -casep-helper [test value default-f & pairs]
(loop [pair pairs]
(cond (empty? pairs)
(default-f)
:else
(let [[keys f] (first pairs)]
(if (exists [x keys] (test value x))
(f)
(recur (rest pairs)))))))
The error was what motivated me to try to rewrite it using reduce
/`reduced`.It is not obvious to me from a minute of reading where that function would cause an infinite loop, unless the infinite loop is in default-f
or f
or like Javascript 😉
reduce won't use the 0 arg arity of that function - supplying default-f as the init arg already accomplishes the right thing
and you can eliminate both the "apply" and the "()" - just the parens suffice
you're using pairs
inside the loop, whereas you probably should be using the (misnamed since it's still a seq) pair
I think.
(loop [[pair & more] pairs]
(if pair
(do ... (recur more))
...))
So an infinite loop happens because recuring with (rest pairs)
presumably always has some values left, since it's what you passed into the function, not the loopHi. I have a clojure test (deftest) running fine at the REPL, but that fails when I run using deps.edn (clj -M:test) with cognitect test runner, because “class not found”. Fixed if I manually compile AOT before running the test. So… is it possible to automate AOT for tests without resorting to a Makefile? Any pointers gratefully received! Thank you
@mark354 Yes, you can add "-e" "(compile,'the.namespace)"
into your :main-opts
for running the tests, ahead of the "-m"
option for the test runner. See https://github.com/seancorfield/cfml-interop/blob/develop/deps.edn#L11-L13 for an example of this.