読者です 読者をやめる 読者になる 読者になる

No Programming, No Life

新しいNPNLです。http://d.hatena.ne.jp/fumokmm/ から引っ越してきました。

Clojureで文字列から動的に関数名を作って実行したいんだが・・・

Clojure 文法

リストの第一要素がシンボルなら関数として起動できるのかな?と思っていたんだがどうも違うらしい。

とりあえず、printlnを例として。(symbol 文字列) でシンボルは作れるらしい。

$ clj
Clojure 1.4.0
user=> (symbol? 'println)
true
user=> (= 'println (symbol (str "print" "ln")))
true

でも、実行しても期待の動作をしてくれない。なんでだろう。

user=> (println "hello")
hello
nil
user=> ((symbol (str "print" "ln")) "hello")
nil
user=> (if true "yes" "no")
"yes"
user=> ((symbol "if") true "yes" "no")
ArityException Wrong number of args (3) passed to: Symbol  clojure.lang.AFn.throwArity (AFn.java:437)

なにか大きな勘違いをしているような気もするが、ひとまずメモとして。
追って調べる。

2012-06-03追記

@さんからメンションがあった。



なるほど、やっぱりシンボルとFnは別物なんだな。今回のやつは load-string を使うのがスマートっぽい。

user=> ((load-string (str "print" "ln")) "hello")
hello
nil

ただ、これだとifとかには使えないのかな?

user=> ((load-string "if") true "yes" "no")
CompilerException java.lang.RuntimeException: Unable to resolve symbol: if in this context, compiling:(null:12) 
id:wamanさんからもコメントがあった
(eval (list (symbol (str "print" "ln")) "hello"))

とすると動きました! いまいち動作を理解してませんけど。

こちらは eval する例ですね。
evalは (source eval) とかで覗いてみると、clojure.lang.Compiler を生で使っているようで、プリミティブな操作っぽいですね。
さらに色々やってみたんですが、倭マンさんはlistしてから全体をevalしてますが、リストの先頭の部分(つまり、関数として起動させる部分)のみをevalする感じでも動くようです。

user=> ((eval (symbol "println")) "hello")
hello
nil

これ、evalしてからsymbolしてるので合成関数にしてしまっても便利かもしれません。

user=> (def eval-and-symbol (comp eval symbol))
#'user/eval-and-symbol
user=> ((eval-and-symbol "println") "hello")
hello
nil