beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
Jim Newton 2020-11-11T13:08:44.004500Z

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

Jim Newton 2020-11-12T09:14:49.028100Z

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.

Jim Newton 2020-11-12T09:18:54.029800Z

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.

Jim Newton 2020-11-12T09:22:12.030Z

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.

Daniel Stephens 2020-11-12T10:28:52.035900Z

(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 🤷

Daniel Stephens 2020-11-12T10:35:09.036100Z

Maybe this would work for you

Daniel Stephens 2020-11-12T10:35:12.036300Z

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

Jim Newton 2020-11-12T11:10:09.037100Z

about the @, that's an interesting thought to avoid having to do a funcall.

Jim Newton 2020-11-12T11:13:06.037400Z

@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]
   ...)

Daniel Stephens 2020-11-12T12:25:07.038700Z

(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 works

Daniel Stephens 2020-11-12T12:25:53.038900Z

otherwise :as couldn't know whether to refer to a and as or just as or nothing

Daniel Stephens 2020-11-12T12:29:55.039100Z

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.

Jim Newton 2020-11-11T13:14:27.005200Z

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

2020-11-11T13:16:09.005300Z

try .invoke

(.invoke (reduce
            (fn [f k]
              (fn []
                (str (f) (k))))
            [(fn [] "q") (fn [] "w") (fn [] "e")]))

✔️ 2
2020-11-11T14:37:13.006700Z

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

2020-11-11T14:37:45.007400Z

In this, Clojure is more like Scheme than Common Lisp. I don't recall whether Scheme has funcall, but I would guess no.

2020-11-11T14:41:33.007900Z

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 letcall that function, e.g.:

(let [f (reduce …)]
  (f))

Jim Newton 2020-11-11T14:44:04.008200Z

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

Jim Newton 2020-11-11T14:45:58.008500Z

@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.

Jim Newton 2020-11-11T14:47:34.008700Z

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`.

2020-11-11T14:51:21.008900Z

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

Nassin 2020-11-11T16:31:45.009500Z

or like Javascript 😉

2020-11-11T19:54:48.010100Z

reduce won't use the 0 arg arity of that function - supplying default-f as the init arg already accomplishes the right thing

1
2020-11-11T19:55:12.010300Z

and you can eliminate both the "apply" and the "()" - just the parens suffice

Daniel Stephens 2020-11-11T21:26:27.010600Z

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 loop

Mark Wardle 2020-11-11T22:48:38.013400Z

Hi. 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

seancorfield 2020-11-11T23:00:06.014500Z

@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.