Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused by "let" in Clojure

Tags:

let

clojure

eval

I just started playing with Clojure, and I wrote a small script to help me understand some of the functions. It begins like this:

(def *exprs-to-test* [  
    "(filter #(< % 3) '(1 2 3 4 3 2 1))"
    "(remove #(< % 3) '(1 2 3 4 3 2 1))"
    "(distinct '(1 2 3 4 3 2 1))"
])

Then it goes through *exprs-to-test*, evaluates them all, and prints the output like this:

(doseq [exstr *exprs-to-test*]
    (do 
        (println "===" (first (read-string exstr)) "=========================")
        (println "Code: " exstr)
        (println "Eval: " (eval (read-string exstr)))
    )
)

The above code is all working fine. However, (read-string exstr) is repeated so I tried to use let to eliminate the repetition like so:

(doseq [exstr *exprs-to-test*]
    (let [ex (read-string exstr)] (
        (do 
            (println "===" (first ex) "=========================")
            (println "Code: " exstr)
            (println "Eval: " (eval ex))
        )
    ))
)

But this works once for the first item in *exprs-to-test*, then crashes with a NullPointerException. Why is the addition of let causing the crash?

like image 278
Tom Dalling Avatar asked Mar 07 '10 03:03

Tom Dalling


2 Answers

You have an extra set of parentheses around the do form. Your code is doing this:

((do ...))

It's trying to execute (as a function call) the value of the entire do form, but do is returning nil, because the last println in the do form returns nil.

Note, your indentation style is non-standard. You shouldn't put the closing parens on their own lines. And let has an implicit do so you don't need one there. Try this:

user> (doseq [exstr *exprs-to-test*]
        (let [ex (read-string exstr)] 
          (println "===" (first ex) "=========================")
          (println "Code: " exstr)
          (println "Eval: " (eval ex))))
=== filter =========================
Code:  (filter #(< % 3) '(1 2 3 4 3 2 1))
Eval:  (1 2 2 1)
=== remove =========================
Code:  (remove #(< % 3) '(1 2 3 4 3 2 1))
Eval:  (3 4 3)
=== distinct =========================
Code:  (distinct '(1 2 3 4 3 2 1))
Eval:  (1 2 3 4)
like image 172
Brian Carper Avatar answered Sep 30 '22 09:09

Brian Carper


I think the other answers are ignoring the elephant in the room: why are you doing this? There are a lot of things in your code that make me worry you are forging on the wrong path through learning Clojure:

  • Using global bindings (exprs-to-test)
  • Using doseq/println to try out code in sequence
  • Using eval

The best way to learn the APIs of Clojure is through the REPL. You should get your environment set up, be it Vim, Emacs, or an IDE such that you can easily move back and forth between static code in text files and an interactive REPL. Here is a good breakdown of a number of Clojure IDEs.

Now, as far as your code goes, a few things to remember. First, there's almost never a good reason to use eval. If you find yourself doing this, ask yourself if its really necessary. Second, remember, Clojure is a functional language and generally you should not need to use the "do" set of macros. The "do" macros are useful when you need to have side effects (in your example, the side effect is the println to *out*) Finally, using global vars should be avoided as well. If you do need to use vars, you should consider using the bindings macro to bind the vars locally to the thread to immutable values so there are no concurrency issues.

I definitely recommend you take the time to pick up Programming Clojure or another deeper reference to LISP to truly understand the shift necessary in the way you think about programming to leverage Clojure effectively. Your small sample here makes me feel as though you are trying to write imperitive code in Clojure, which is not going to work well at all.

like image 41
Greg Fodor Avatar answered Sep 30 '22 09:09

Greg Fodor