tags:

views:

256

answers:

3

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?

+3  A: 

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)
Brian Carper
That fixed it. Thanks for the indenting style tip too.
Tom Dalling
+1  A: 

Brian already answered your question, so I just want to give you some general pointers for the let-form:

Michael Kohl
+1  A: 

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.

Greg Fodor
I know the globals are just out of control in this 10 line script, and I won't use "do" macros and eval anymore, even in situations where they are actually necessary. I'll take your advice about the REPL too because I have a flawless memory of languages I've only used for 10 minutes, and I don't need to save my work for later reference. As a seasoned Clojure professional, your righteous non-answer fills me with shame. I will try harder next time.
Tom Dalling
What a disgustingly snarky and sarcastic comment. I was trying to help. Shame on you.
Greg Fodor
I don't think it's appropriate to ignore the question and rag on about how a beginner's code isn't perfect. Felt more like you were blowing your own trumpet than trying to help.
Tom Dalling
I think I understand. You feel it's inappropriate for someone to post higher level recommendations based upon the perceived foundations of a question. But it's perfectly appropriate to post a sarcastic reply insulting someone who spent some time typing out a reasonable response trying to *help you*. You find it insulting that people question your questions, even if you may be on the wrong track. Apparently you can't even see the value that other folks learning Clojure may wander by here see your "imperfect" code and think that is the appropriate way to do things in general.
Greg Fodor
For the record, I started writing code in Clojure this past weekend, so I am not the stereotype you have in your head of some know-it-all holier-than-thou programmer. I am a beginner who was trying to help another beginner (after having learned from my initial mistakes.) So sorry to bruise your ego.
Greg Fodor
Yeah, sorry, I did go a bit overboard.
Tom Dalling
Thanks, appreciate it. Good luck on your path to learning Clojure!
Greg Fodor