4

I'm trying to build a function out of the results of one already evaluated function and a list of other functions. If I partially construct it it works fine, but every time I try to construct the entire thing it fails.

(defn play [] 
    (let [eval1 (eval '(+ 1 1))]
        (eval `(->> ~eval1 (+ 5) (- 2) (* 7)))
    )
)

This fails:

(defn play []
    (let [eval1 (eval '(+ 1 1))
          eval2 '((+ 5) (- 2) (* 7))]
        (eval (cons (cons eval1 eval2) ->>))
     )
)

I've tried numerous other ways to put this together and I think it boils down to me not quite understanding this aspect of clojure. What's the best way to go about this problem? Thanks!

  • 1
    ->> is a macro. You can not take the value of a macro into an eval-function because macros only exist in the compile-time context. – WeGi Mar 31 '14 at 15:34

3 Answers3

4

You generally don't want to use eval. However, maybe this time you do, for whatever reason (understanding Clojure's reader is a valid reason). So the answer depends on what you mean by "this problem."

Part 1 -- with eval

A. Webb's answer is perfectly fine. If the example you gave is just so you can learn how the Clojure reader works, the rest of my answer may be irrelevant to you.

Part 2 -- without eval

If by "this problem" you mean constructing a function from a list of functions and a value, you don't want eval. Replace (eval '(+ 1 1)) with (+ 1 1). Then because you want a list of functions, you can use #(+ 5 %) etc. or (partial + 5) etc. and store those in a list.

->> is a threading macro. This means it is executed at compile time, to transform its whole S-expression. But in your failed code, it doesn't have an S-expression at compile time -- you would be attempting to craft its S-expression at run time by consing it onto another list.

You could write another macro to do what you want here. But in general, when I confront the problem of applying a list of functions to an initial value, I use reduce:

(defn play []
    (let [eval1 (+ 1 1)
          eval2 (list #(+ 5 %) #(- 2 %) #(* 7 %))]
        (reduce #(%2 %) eval1 eval2)))

In fact, this comes up often enough in my code that most of my projects have this in their core.clj:

(defn freduce
   "Given an initial input and a collection of functions (f1,..,fn),
   This is logically equivalent to ((comp fn ... f1) input)."
   [in fs]
   (reduce #(%2 %) in fs))

Finally, if you want to construct a function rather than its evaluation, then wrap the return value with fn:

(defn play []
    (let [in (+ 1 1)
          fs (list #(+ 5 %) #(- 2 %) #(* 7 %))]
        (fn [] (freduce in fs))))

user=> ((play))
-35
Community
  • 1
  • 1
galdre
  • 2,319
  • 17
  • 31
  • 1
    The first bit is perhaps misleading out of context. In general, you would be working with symbols doing compile-time manipulation. Happily, symbols resolving to macros within the body of other macros work just fine. – A. Webb Mar 31 '14 at 16:22
  • Thanks, the second part of your answer describes exactly the problem I was aiming to solve. –  Apr 01 '14 at 05:21
1

The two problems at hand with your second play are (1) you have not quoted ->> so Clojure will complain about "tak[ing] the value of a macro", you want to be doing symbolic manipulation instead; and (2) you are consing your list together in the wrong order.

Try

(defn play []
  (let [eval1 (eval '(+ 1 1))
        eval2 '((+ 5) (- 2) (* 7))]
    (eval (cons '->> (cons eval1 eval2)))))

Other things to consider would be to use normal functions or macros, use syntax quote to capture namespaces, use list* instead of two cons.

A. Webb
  • 26,227
  • 1
  • 63
  • 95
0

(+ 1 1) is not a function, '((+ 5) (- 2) (* 7)) is not a list of functions.

If you have a list of functions, say

(def fns (list (partial + 5) (fn [x] (- 2 x)) #(* 7 %)))

you can achieve your goal of "constructing a function from a list of functions"

(def master-fn (->> fns reverse (apply comp)))

Then you can invoke it with "the result of one already evaluated function [call]":

(master-fn (+ 1 1))
;; => -35
Leon Grapenthin
  • 9,246
  • 24
  • 37