Fork me on GitHub
#beginners
<
2022-06-24
>
Jim Strieter07:06:44

When I do this:

(fn (... (f2 (f1 some-map))...))
code compiles fine. I prefer to do this:
(-> some-map
    f1
    f2
    ...
    fn)
because it's more readable. But I keep getting this error:
Syntax error (NullPointerException) compiling at (/tmp/form-init7026027282884177930.clj:1:73).
What am I doing wrong?

jumar07:06:00

What are the second ... in your first example?

Jim Strieter08:06:47

second ...'s are just more parentheses

Ed08:06:21

I think you've removed too much detail in simplifying your example. There might be a typo in the second one?

Jim Strieter08:06:09

@U01AKQSDJUX posted response in main thread

Mno07:06:52

It's impossible to know without knowing each of those functions. But you can try using macroexpand on the bottom one to see what the macro expands into so you can see the differences

Mno07:06:53

NullPointerexceptions usually means something was null when it shouldn't be, so maybe that will help.

Jim Strieter08:06:48

When I try this at REPL:

(-> {:a 42} (fn [s] (conj s {:b 99})))
I'm expecting this:
{:a 42 :b 99}
But I get this:
Syntax error macroexpanding clojure.core/fn at (/tmp/form-init10684189129508105122.clj:1:1).
{:a 42} - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
{:a 42} - failed: (or (nil? %) (sequential? %)) at: [:fn-tail :arity-n] spec: :clojure.core.specs.alpha/params+body

lispyclouds08:06:46

So passing anonymous fns to a threading macro has some different expansion effects than you were expecting, see https://stackoverflow.com/questions/62948630/clojure-functions-and-thread-macro that could give you some clues 🙂

Martin Půda08:06:08

Your example expands to:

(fn {:a 42} [s] (conj s {:b 99}))

Martin Půda08:06:08

Use one of these (the first one is the best):

(-> {:a 42} 
    (conj {:b 99}))

=> {:a 42, :b 99}

(-> {:a 42}
    ((fn [s] (conj s {:b 99}))))

=> {:a 42, :b 99}

(-> {:a 42}
    (#(conj % {:b 99})))

=> {:a 42, :b 99}

🙌 1
👍 2
agile_geek08:06:45

As a matter pf style I’d not use a threading macro at all for anything less than 3 levels of parens (i.e. 1 as in this case). So my answer would be:

(conj {:a 42} {:b 99})

agile_geek08:06:22

or this:

(conj (first [{:a 42}]) {:b 99})
in preference to this:
(-> [{:a 42}] first (conj {:b 99}))

agile_geek08:06:10

Any thing more than one nested form I’d probably use a threading macro.

Jim Strieter08:06:21

@U05390U2P There will be > 10 nested function calls. Right now all I want to do is use threading macro correctly

Jim Strieter08:06:02

What I want to do is this:

(-> some-starting-val
    f1
    f2
    f3
    ...
    fn)

agile_geek08:06:06

e.g.

(->> "string"
     str/upper-case
     (assoc {:a 42} :string)
     (conj [{:b 99}]))
in preference to this:
(conj [{:b 99}] 
  (assoc {:a 42} :string (str/upper-case "string")))

🙌 1
Ed08:06:37

If all those functions are named (as in defined with defn) and take one argument then your (-> some-map f1 f2 f3) form will work

agile_geek08:06:18

@U014Z9N3UTS you can do what you show if you don’t use anonymous fn’s in the threading macro. So either define the fns f1, f2, etc. using defn’s OR in a let or letfn that encloses the threaded form:

(let [f1 (fn [x] ...)
      f2 (fn [x] ...)
      f3 (fn [x] ...)
      ...]
  (-> some-starting-val
    f1
    f2
    f3
    ...
    fn))

Ed08:06:43

You can use macroexpand-1 to show what the macro will expand to

👍 1
Joshua Suskalo16:06:56

Something else to be aware of is if you really need to have the argument be in some place besides the first argument of a function call (which is the main target of using an anonymous function here), you can use as-> For example

(-> {:your :data}
    (some-func-in-arg-1 {:extra :data})
    (as-> a (some-func-in-arg-2 {:more :data} a))
    another-func)

seancorfield16:06:39

(although that case could use ->> instead of as->)

Jim Strieter04:06:59

The mistake I made is that I passed in functions without wrapping in parentheses. For example, I did this:

(-> x f1 f2 f3)
but I needed to do this:
(-> x (f1) (f2) (f3))
Thank you everybody for helping! 🙂

agile_geek19:06:44

You shouldn't need to wrap the fns in parens if they only take one argument so I'm a little puzzled. Unless you're defining the fns anonymously in line in the threading macro rather than defn or let. In which case wrapping them in parens would resolve it but it's not idiomatic.

Joshua Suskalo20:06:02

That's what the discussion started with, anonymous functions defined inline being used in thread pipelines.

eltonlaw16:06:03

Hi, why can’t recur run in a try block?

hiredman17:06:04

inside a try is not a tail

hiredman17:06:26

the tail of an expression, is the last subexpression in it to be evaluted/run before the evaluation of the entire expression is complete

👍 1
hiredman17:06:53

a try expression semantically is like pushing an exception handler on to the stack of handlers, then evaluating some expression, then popping the pushed exception handler off

👍 1
hiredman17:06:43

so the inner expression isn't a tail

hiredman17:06:46

https://clojure.atlassian.net/browse/CLJ-31 was maybe my first clojure patch, and started the tradition of my patches breaking things that need to be fixed in subsequent patches https://clojure.atlassian.net/browse/CLJ-667

❤️ 1