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追記
@halcat0x15aさんからメンションがあった。
@fumokmm ('println "hoge")でhogeが出力されないことと同じで、SymbolとFnは別のものです。
2012-06-02 15:12:07 via web to @fumokmm
@fumokmm 動的にやるならば、((load-string "println") "hoge")とか((eval 'println) "hoge")でしょうか。
2012-06-02 15:13:51 via web to @fumokmm
なるほど、やっぱりシンボルと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