tags:

views:

434

answers:

2

I'm taking a look at the excellent Clojure tutorial here. In one of the examples it has Clojure code along the following lines:

(def vowel? (set "aeiou"))

This makes vowel return true for vowels and false for consonants:

(vowel? (first "abc")) ; => true
(vowel? (first "cba")) ; => false

Why is this? I'm assuming it has something to do with the question mark behind the variable name. Couldn't find anything right away in the tutorial...


Edit I just realized vowel? doesn't return true or false but rather the element itself or nil. See my own answer.

+5  A: 

Aha! I ended up finding it out myself. It doesn't actually return true or false, rather it returns the first occurrence in the set, or nil if it doesn't occur.

And because you can use this as a condition (nil being handled as false, and non-nil as true), this works as a nice little hack for checking if a string contains a letter.

(vowel? (first "abc")) ; => "a"
(vowel? (first "cba")) ; => nil

(if (vowel? (first "abc"))
       (println "yay")
       (println "oops"))  ; => "yay"
Epaga
Sounds right to me. The question mark indicates it can be used as a test. You'll often see an exclamation point on functions that have side effects. They're not part of the language, they're lisp traditions (so you know the safe functions from the "dangerous" ones).
MBCook
To make it clear, ? and ! are just characters like a and b, and you can use them for naming symbols (as are \, /, *, -, + ...). There are traditions for naming certain symbols in Lisp, but there are small differences between the dialects.
Svante
ah, thanks @svante! i thought there was something magical about the "?" :)
Epaga
+10  A: 

This is perfectly analogous to how maps (the most natural objects in Clojure) work. When the map is called as a function, it works as a mapping:

user=> (def ob {:foo "bar", :bar :baz, :qwerty 42})
#'user/ob
user=> (ob :foo)
"bar"

So it makes sense that a Clojure set can be called as a function, and will work as a membership test. By the way, if you use keywords (those things that start with a colon) as the keys of a mapping, they also work as similar functions, so you can do

user=> (:bar ob)
:baz

and even the same thing with sets of keywords:

user=> (def vowel-keywords (set [:a :e :i :o :u]))
#'user/vowel-keywords
user=> (:a vowel-keywords)
:a
user=> (:b vowel-keywords)
nil

But, again, this latter trick only works with keywords, not anything else that you might use as keys in a mapping or members in a set.

Jouni K. Seppänen