combinator
コンビネータとは自由変数を含まないλ項のことをいいますが、Yコンビネータ以外あまり取り上げられていない気がするのですが、これも100年に1度の金融危機の影響かとか、WBC楽しみですねだとか、そういえば今年雪祭り行けなかったんですよとか、最近携帯変えましたとか色々あるのですが、自分の練習もかねて、そんなコンビネータ達と戯れたいと思います。
沢山ある!
Y以外にも広く知られている(?)コンビネータはいっぱいあるようですが、その中からいくつか。
- I ≡ λx.x
- K ≡ (λx.(λy.x)) //以下、簡単に λx y.x と書きます
- F ≡ λx y.y
- M ≡ λx.xx
- S ≡ λx y z.xz(yz)
- B ≡ λx y z.x(yz)
- C ≡ λx y z.xzy
- L ≡ λx y.x(yy)
- Y ≡ (λx.xx)(λx.xx) (SLL)
(当てるアルファベットが違うのはたまに見かけますが、大体こんな感じ)
true, false, if を表現してみる
TRUE = λx y.x (K)
FALSE = λx y.y (KI,F)
IF = λx y z.xyz
falseがなぜ空気いらない(Kuuki Iranai)なのか少し補足。
KI => (λx.(λy.x))I
=> (λy.I)
Iは(λx.x)なので、
=> (λy.(λx.x)).
これを簡単に書くと λy x.x となります。 F コンビネータと一緒!
これらをF#(OCaml)で書いてみる。
> let K x y = x;; val K : 'a -> 'b -> 'a > let I x = x;; (* id関数とほぼ同じ *) val I : 'a -> 'a > let IF x y z = x y z;; val IF : ('a -> 'b -> 'c) -> 'a -> 'b -> 'c (* テスト用関数。"foo"の時に真を返して、そうでなければ偽。 *) > let isFoo = function - | "foo" -> K - | otherwise -> (K I) ;; val isFoo : string -> ('a -> 'a -> 'a) > IF (isFoo "bar") "true!" "false...";; val it : string = "false..." > IF (isFoo "foo") "true!" "false...";; val it : string = "true!"
Y!
先に挙げたコンビネータのうち、Yは少しややこしいのでここでそれを。
不動点オペレータ(fixed-point operator)などとも呼ばれるY氏。
これを以下のように定義してみる。
fixf = (λx.f(xx))(λx.f(xx))
燃えさかる赤いλ式に、クールなブルーλ式を渡してみると・・・
=> f( (λx.f(xx))(λx.f(xx)) )
となるのが分かります。
ん?下線部分どこかで見たことあるような・・・?*1
なんとfixの定義!
=> f( fixf )
これが、 f(f(f(fixf))) などと括弧いいことになっていくわけです。
fix式は関数fの引数として渡されるわけだから、それを評価するかどうかはfが決めます。なので、書いた瞬間コンソールに無限に文字列が表示される、といったことにはなりません。
色々な言語で・・・
色々な言語を勉強中なのであれこれ書いてみます。(実はこれがメインだったりする)
意外なことに、まずSmalltalkで!(Dolphin Smalltalk)
SとYを定義してみた。
| S | S := [:x :y :z | x value: z value: (y value: z)]. ^ S value: [:x :y | x*y] value: [:x | x+10] value: 10. "=> 200" | Y | Y := [ :f | [:x | f value: [:a | (x value: x) value: a]] value: [:x | f value: [:a | (x value: x) value: a]] ]. ^ ( Y value: [:f | [:n | (n > 1) ifTrue: [n * (f value: (n-1))] ifFalse: [1]]] ) value: 5 "=>120"
何とか動いた〜。
お次はLisp。
Common Lispでやると爽やかじゃなくなるので、まずSchemeから。
> (define fix (lambda (f) ((lambda (x) (f (lambda (arg) ((x x) arg)))) (lambda (x) (f (lambda (arg) ((x x) arg)))) ))) ;; こっちの方がまだ見やすい・・・? > (define (Y f) (let ((e (lambda (x) (f (lambda (y) ((x x) y)))))) (e e) ))
> (define foo (fix (lambda (f) (lambda (n) (if (= n 0) '() (cons n (f (- n 1))) ))))) > (foo 10) (10 9 8 7 6 5 4 3 2 1)
爽やかでしたね。仕方ないので次はCommon Lispで書いてみることに
CL-USER> (setq Y (symbol-macrolet ((e #'(lambda (x) (funcall f #'(lambda (&rest args) (apply (funcall x x) args) ))))) #'(lambda (f) (funcall e e)) )) CL-USER> (setq fn #'(lambda (f) #'(lambda (x acc) (if (< x 1) acc (funcall f (1- x) (cons x acc)) )))) CL-USER> (funcall (funcall Y fn) 10 nil) (1 2 3 4 5 6 7 8 9 10)
こっちは、&rest と apply を使うことで複数個の引数を使えるようにした。
ちなみにCommon Lispのlabelsや、Schemeのletrecは不動点オペレータとして見ることもできる。
お次はHaskell。
Prelude> let y f = let x = f x in x Prelude> y (\f n -> if n>1 then n * f (n - 1) else 1) 5 120
遅延評価すごい! let x = f x とかシビれる。
勝手にカリー化してくれるので複数の引数にも対応できます。
そんなHaskellには標準で不動点オペレータが用意されているのでした。
Prelude> :m Control.Monad.Fix Prelude Control.Monad.Fix> :t fix fix :: (a -> a) -> a Prelude Control.Monad.Fix> fix (\f n -> if n>1 then n * f (n - 1) else 1) 5 120
最後に、Ocaml.
以前こんなの見つけたのですが、自分自身まだ良く理解してないので貼っておいて勉強します・・・。
type使えば何とかできるんじゃないかな?と漠然と思っていたけど、想像と全然違った。。
type 'a recc = { out : 'a recc -> 'a } let inn y = { out = y } let out z = z.out let y f = (fun x a -> f (out x x) a) (inn (fun x a -> f (out x x) a))Fixed-point combinator - Wikipedia
あと、いげ太さんの日記にもメモがあるので、あわせて読みたい。
結論
長々書いておいて結局何が言いたかったかというと、
KYは空気読めないのではなく、KコンビネータとYコンビネーt(ry
若い女の子の間ではλ計算とか当たり前らしい・・・!
僕自身初心者なので変なところとかあるかも。。そんな時はご指摘なんて頂けると嬉しいです。
*1:式の部分をリンクかと思って何気なくマウスオーバーした人はコメント欄にて懺悔すること