与えられた引数が並んだ「リスト」を返す
(list 'a 'b 'c) ;-> (a b c) (list 'f 1 2 3) ;-> (f 1 2 3) (list '+ 1 2) ;-> (+ 1 2)
与えられたリストを評価する
(eval '(+ 1 2)) ;-> 3 (eval (list '+ 1 2)) ;-> 3
if は存在する
(if true "ok" "ng") ;-> "ok" (if false "ok" "ng") ;-> "ng"
unless は無い
(unless false "ok" "ng") ;-> "ok" (unless true "ok" "ng") ;-> "ng" ; こういうのが欲しいな
(defn unless-f [cond then else] (if cond else then))
(unless-f false "ok" "ng") ;-> "ok"
お?これでいいのでは?
(unless-f false (println "ok") (println "ng")) ;-> ; ok ; ng ; nil
(println "ng") が実行されてしまった
unless-f は関数である
引数は全て「評価されてから」関数に渡される
なので unless-f は false, nil, nil という引数を受け取っている
※println 関数が評価された結果は nil になる
(unless-f false (fn [] (println "ok")) (fn [] (println "ng"))) ;-> #<user$eval678$fn__679 user$eval678$fn__679@2e1ddadc>
だめだ関数を渡しただけで実行されてない
(defn unless-f-2 [cond then else] (if cond (else) (then))) (unless-f-2 false (fn [] (println "ok")) (fn [] (println "ng"))) ;-> ; ok ; nil
これで出来た
(unless-f-2 false "ok" "ng") ;-> ; ClassCastException java.lang.String cannot be cast to ; clojure.lang.IFn user/unless-f-2 (NO_SOURCE_FILE:2)
当然関数以外も関数として扱おうとするのでその旨のエラーが出る
(defn unless-f-3 [cond then else] (if cond (if (function? else) (else) else) (if (function? then) (then) then)))
さて関数かどうか判定する function? 関数に相当するものを調べるか -> 見つからない
そろそろつらみが溢れてくる (´・_・`)
そもそも unless の度に処理を関数として作り直すとか面倒臭すぎる
if と同じで出来なければ意味無いやん
そこでマクロというものがあるそうですよ
(defmacro unless [cond then else] (list 'if cond then else))
(unless false (println "ok") (println "ng")) ;-> ; ok ; nil
マクロによってコードそのもの (リスト) が生成されて実行されている
macroexpand-1 という関数でマクロの展開を追うことが出来る
(macroexpand-1 '(unless false (println "ok") (println "ng"))) ;-> (if false (println "ng") (println "ok"))
これはマクロを一段階だけ展開する関数
全て展開するには macroexpand を使う
今回はどちらも同じ
(if false (println "ng") (println "ok"))
我々の望みは確かにこの式である
これが評価されれば良い
多分これと等価
(eval (macroexpand form))
評価を抑制する
'(x y z) ;-> (x y z) (x y z) ;-> ; CompilerException java.lang.RuntimeException: ; Unable to resolve symbol: x in this context, ; compiling:(NO_SOURCE_PATH:1:1)
(macroexpand-1 (unless false "ok" "ng")) ;-> "ok" (macroexpand-1 "ok") ;-> "ok"
先に (unless ...) の部分が評価されてしまったことになる
マクロが含まれたリストそのものを渡さなければならない
なので quote を付ける
(macroexpand-1 '(unless false "ok" "ng")) ;-> (if false "ng" "ok")
(defmacro unless [cond then else] (list if cond else then))
こうだとどうなるか?
;-> ; CompilerException java.lang.RuntimeException: ; Unable to resolve symbol: if in this context, ; compiling:(NO_SOURCE_PATH:2:3)
if ってシンボルが無いと言われる
※ごめんなさいこの辺よく理解し切れてません orz
実はこれ単体では quote とまったく同じ
` (backquote) は ~ (tilde) と組み合わさったときに意味がある
※なお CommonLisp では , (comma) がこの機能である
※Clojure では comma はただの目印でしかない (あっても無視される)
(def x 1) (def y 2) '(x y x y) ;-> (x y x y) `(x y ~x ~y) ;-> (user/x user/y 1 2)
`() の中では,~ が付いたものだけは評価される
Common Lisp あるいは Scheme では例えば x が 1 で y が 2 として
('x 'y x y) ;-> (x y 1 2)
`(x y ,x ,y) ;-> (x y 1 2)
は等価である
Clojure では
('x 'y x y) ;-> (x y 1 2)
`(x y ~x ~y) ;-> (user/x user/y 1 2)
のように名前空間で修飾される
Thanks for @omasanori
(defmacro unless [cond then else] `(if ~cond ~else ~then))
これで (list 'if cond else then) と等価になる
※と思う
マクロ難しい
理解出来てる自信がない
コードを生成するコードが通常のコードとほぼ同じレベルで書けてしまう (template だの proc だの Proc だのじゃない)
メタプログラミングたのしい (白目)
unless が作れた
これは他の言語では無理なのである
マクロ書くな
マクロじゃなければ出来ないこと以外でマクロ書くな
マクロの再帰呼び出しも面白いです
Clojure はリーダマクロがプログラマによって作れない (混沌抑制)
リスト操作系の念能力をもっと伸ばさなければならない
cons car cdr だけでは死ぬ
quote とそもそも「シンボル」の正しい理解を得よう
if がどうして 'if にしないとダメだったのかの辺り
1986 年の本だけど読んでるとためになる辺り凄い
Lisp って楽しいよね!
いっしょに楽しもうよ!
つづく
(Lisp との戦いはまだ始まったばかりだ!)