"in" macro

3 views
Skip to first unread message

Parth Malwankar

unread,
Aug 17, 2008, 10:08:52 AM8/17/08
to Clojure
I am trying to create a "in" macro.
(in 'a 'b 'a 'c) => true.

This is what I did.

(defmacro in [obj & choices]
`(let [o# ~obj]
(or ~@(map (fn [x] `(= o# ~x)) choices))))

It doesn't seem to be working.

user=> (load-file "utils.clj")
nil
user=> (macroexpand-1 '(in 'a 'a 'b 'c))
(clojure/let [o__2199 (quote a)] (clojure/or (clojure/= o__2198 (quote
a)) (clojure/= o__2198 (quote b)) (clojure/= o__2198 (quote c))))

Shouldn't the first o# be equal to the second i.e o__2199 and o__2198?
What I am
doing wrong here.

Error log below.

Thanks for your help.
Parth


user=> (in 'a 'a 'b 'c)
java.lang.Exception: Unable to resolve symbol: o__2198 in this context
clojure.lang.Compiler$CompilerException: NO_SOURCE_FILE:9: Unable to
resolve symbol: o__2198 in this context
at clojure.lang.Compiler.analyze(Compiler.java:3669)
at clojure.lang.Compiler.analyze(Compiler.java:3627)
at clojure.lang.Compiler.access$100(Compiler.java:37)
at clojure.lang.Compiler$HostExpr$Parser.parse(Compiler.java:
726)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:3814)
at clojure.lang.Compiler.analyze(Compiler.java:3654)
at clojure.lang.Compiler.analyze(Compiler.java:3627)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:3809)
at clojure.lang.Compiler.analyze(Compiler.java:3654)
at clojure.lang.Compiler.access$200(Compiler.java:37)
at clojure.lang.Compiler$LetExpr$Parser.parse(Compiler.java:
3443)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:3814)
at clojure.lang.Compiler.analyze(Compiler.java:3654)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:3804)
at clojure.lang.Compiler.analyze(Compiler.java:3654)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:3804)
at clojure.lang.Compiler.analyze(Compiler.java:3654)
at clojure.lang.Compiler.analyze(Compiler.java:3627)
at clojure.lang.Compiler.access$100(Compiler.java:37)
at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:
3340)
at clojure.lang.Compiler$LetExpr$Parser.parse(Compiler.java:
3454)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:3814)
at clojure.lang.Compiler.analyze(Compiler.java:3654)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:3804)
at clojure.lang.Compiler.analyze(Compiler.java:3654)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:3804)
at clojure.lang.Compiler.analyze(Compiler.java:3654)
at clojure.lang.Compiler.analyze(Compiler.java:3627)
at clojure.lang.Compiler.access$100(Compiler.java:37)
at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:
3340)
at clojure.lang.Compiler$FnMethod.parse(Compiler.java:3187)
at clojure.lang.Compiler$FnMethod.access$1200(Compiler.java:
3098)
at clojure.lang.Compiler$FnExpr.parse(Compiler.java:2729)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:3812)
at clojure.lang.Compiler.analyze(Compiler.java:3654)
at clojure.lang.Compiler.eval(Compiler.java:3845)
at clojure.lang.Repl.main(Repl.java:75)
Caused by: java.lang.Exception: Unable to resolve symbol: o__2198 in
this context
at clojure.lang.Compiler.resolveIn(Compiler.java:3967)
at clojure.lang.Compiler.resolve(Compiler.java:3928)
at clojure.lang.Compiler.analyzeSymbol(Compiler.java:3911)
at clojure.lang.Compiler.analyze(Compiler.java:3642)
... 36 more
user=>

Chouser

unread,
Aug 17, 2008, 11:39:17 AM8/17/08
to clo...@googlegroups.com
On Sun, Aug 17, 2008 at 10:08 AM, Parth Malwankar
<parth.m...@gmail.com> wrote:
>
> Shouldn't the first o# be equal to the second i.e o__2199 and o__2198?
> What I am doing wrong here.

They are only the same within the same backqoute:

user=> `(o# o# o#)
(o__2127 o__2127 o__2127)

There they're all the same.

user=> `(o# o# ~`(o# o#) o#)
(o__2151 o__2151 (o__2150 o__2150) o__2151)

In that example, the inner backqoute gives different values.

Instead you may have to use (gensym) manually. ...or rewrite your
macro to use a single backquote expression:

(defmacro in [obj & choices]

`(some #{~obj} (list ~@choices)))

--Chouser

Parth Malwankar

unread,
Aug 17, 2008, 1:01:24 PM8/17/08
to Clojure


On Aug 17, 8:39 pm, Chouser <chou...@gmail.com> wrote:
> On Sun, Aug 17, 2008 at 10:08 AM, Parth Malwankar
>
> <parth.malwan...@gmail.com> wrote:
>
> > Shouldn't the first o# be equal to the second i.e o__2199 and o__2198?
> > What I am doing wrong here.
>
> They are only the same within the same backqoute:
>
> user=> `(o# o# o#)
> (o__2127 o__2127 o__2127)
>
> There they're all the same.
>
> user=> `(o# o# ~`(o# o#) o#)
> (o__2151 o__2151 (o__2150 o__2150) o__2151)
>
> In that example, the inner backqoute gives different values.
>
> Instead you may have to use (gensym) manually.  ...or rewrite your
> macro to use a single backquote expression:

Makes sense. The following using the gensym worked correctly.

(defmacro in [obj & choices]
(let [o (gensym)]
`(let [~o ~obj]
(or ~@(map (fn [x] `(= ~o ~x)) choices)))))


>
> (defmacro in [obj & choices]
>   `(some #{~obj} (list ~@choices)))

Thats a neat trick with sets.

Newbie question.
I am not sure I understand how it actually works.
Any pointers? The following also worked
but I don't quite know what to make of it.

user=> (#{1} 1)
1
user=> (#{1} 2)
nil

I also did not know about 'some'. Thanks.

>
> --Chouser

Moxley Stratton

unread,
Aug 17, 2008, 2:26:13 PM8/17/08
to clo...@googlegroups.com

That is equivalent to the following:

user=> (get #{1} 1)
1
user=> (get #{2} 1)
nil

Sets can act as functions of their elements (see bottom of http://clojure.org/data_structures)
.

The same thing works with maps:

user=> ({"1" 1} "1")
1
user=> ({"1" 1} "2")
nil

It may look strange at first, but it is convenient. Stranger still is
that you can use a keyword (or symbol) as a function and the set as
the argument:

user=> (:a #{:a})
:a
user=> (:b #{:a})
(:b #{:a})
nil

Again, the same thing works with maps:

user=> (:a {:a "a"})
(:a {:a "a"})
"a"
user=> (:b {:a "a"})
nil

Moxley

Randall R Schulz

unread,
Aug 17, 2008, 4:57:11 PM8/17/08
to clo...@googlegroups.com
On Sunday 17 August 2008 07:08, Parth Malwankar wrote:
> I am trying to create a "in" macro.
> (in 'a 'b 'a 'c) => true.
>
> ...

Apart from the pedantic value in making it work, why would one want this
functionality in a macro rather than a plain old function?

Randall Schulz

Parth Malwankar

unread,
Aug 18, 2008, 12:57:01 AM8/18/08
to Clojure
I can't think of a good way to do this with a function.
If we do:

(defn in2 [obj & choices]
(let [o obj]
(map (fn [x] (= o x)) choices)))

We get:

user=> (in2 'a 'a 'b 'c)
(true false false)

Which is somewhat correct but the problem is its not a
short circuited evaluation like 'or', so the second
and third tests are also done.

Also, we now are left with the problem of finding
if true is present in the result of in2.
'apply'ing 'or' to the result doesn't work because
'or' is a macro and we can't apply macros.

Another nice way to achieve this is:
user=> (contains? (set ['a 'b 'c]) 'a)
true

I didn't know about 'contains?' ... if I know about this
I wouldn't have bothered with in, but 'in' was a good
exercise.

Clojure seems to be having a lot of cool utility methods
that we normally end up writing and putting in a utils.lisp.

> Randall Schulz

Stephen C. Gilardi

unread,
Aug 18, 2008, 1:30:53 AM8/18/08
to clo...@googlegroups.com

On Aug 18, 2008, at 12:57 AM, Parth Malwankar wrote:

> Which is somewhat correct but the problem is its not a
> short circuited evaluation like 'or', so the second
> and third tests are also done.

Here's another way that does short circuit:

user=> (defn in3 [obj & items] (some #(= % obj) items))
#'user/in3
user=> (in3 'a 'c 'd 'e)
nil
user=> (in3 'a 'c 'd 'a)
true
user=>

You would need a macro if you wanted to delay evaluation of the "items".

--Steve

Graham Fawcett

unread,
Aug 18, 2008, 11:43:05 AM8/18/08
to clo...@googlegroups.com
On Mon, Aug 18, 2008 at 12:57 AM, Parth Malwankar
<parth.m...@gmail.com> wrote:
> I can't think of a good way to do this with a function.
> If we do:
>
> (defn in2 [obj & choices]
> (let [o obj]
> (map (fn [x] (= o x)) choices)))
>
> We get:
>
> user=> (in2 'a 'a 'b 'c)
> (true false false)
>
> Which is somewhat correct but the problem is its not a
> short circuited evaluation like 'or', so the second
> and third tests are also done.

You could use:

(first (filter identity (map ...)))

which will return the first 'true' in the map, or nil if none is
found. Since filter and map are both lazy, only the needed tests are
performed.

It's still not short-circuiting, though, if your sequence is strict.
In your example, "choices" is a sequence over an array, meaning that
all the values in "choices" have all been evaluated before the (map)
call.

> Clojure seems to be having a lot of cool utility methods
> that we normally end up writing and putting in a utils.lisp.

Agreed!

Graham

Rich Hickey

unread,
Aug 25, 2008, 5:14:17 PM8/25/08
to Clojure


On Aug 18, 12:57 am, Parth Malwankar <parth.malwan...@gmail.com>
wrote:
These are all fine:

(some #(= % obj) items)
(some #{obj} items)
(#{'a 'b 'c} obj) ;for non-nil/false
((set items) obj) ;ditto
(contains? #{'a 'b 'c} obj) ;works for nils/false too

Note in particular the 'set trick' works for sets with more than one
element, and most directly represents what you mean by 'in' - is this
value in this set?

Rich
Reply all
Reply to author
Forward
0 new messages