あどけない話

インターネットに関する技術的な話など

Haskell で Y コンビネータ

Haskell では、Y コンビネータが作れないと誤解している人がいるので、できることを示すと同時に、これまで学んだことをまとめてみます。

遅延評価を活かした Y コンビネータ

関数名を用いた再帰を使ってよいなら、Haskell では遅延評価のおかげで、Y コンビネータを定義である Y x = x (Y x) の通りに書けます。

y :: (a -> a) -> a
y x = x (y x)

Y コンビネータ用の階乗を定義してみましょう。

fact :: Num a => (a -> a) -> a -> a
fact = \f n -> if n == 0 then 1 else n * f (n-1)

以下のように動きます。

y fact 424

でも、この階乗は Haskell っぽくないので、入り口で分岐するように書き直してみます。

fact :: Num a => (a -> a) -> a -> a
fact _ 0 = 1
fact f n = n * f (n-1)

fact という名前では、再帰してませんよ。これも期待通りに動きます。

y fact 424

型推論をごまかす Y コンビネータ

定義通りの実装では、Y コンビネータ自身は再帰を使って定義しました。ラムダ計算がチューリング完全であることを示すのが目的であれば、この定義はインチキです。ラムダ計算には、名前を使った再帰がないからです。名前を使った再帰がない条件の下に、Y コンビネータを作らないといけません。

よく Scheme とかで実装された Y コンビネータは、ゴチャゴチャしていて分かりにくいと思うので、分割して実装してみましょう。

import Unsafe.Coerce

s :: (a -> b -> c) -> (a -> b) -> a -> c
s x y z = x z (y z)

l :: (a -> b) -> (c -> a) -> b
l x y = x (y (unsafeCoerce y))

y :: (a -> a) -> a
y = s l l

Y コンビネータの実装方法はたくさんあります。これは SLL と表現できる Y コンビネータです。どの関数も再帰を使っていないことに注意して下さい。

L の定義は、本当は L x y = x (y y) です。(y y)の部分が自己言及になって、GHC ではこの部分の型をうまく処理できません。そこで、unsafeCoerce で型推論をごまかしています。

実際に動くことを確かめておきましょう。

y fact 424

上記の定義では、いきなり L を使いましたが、もっと根源的な S と K のみから出発して、Y にたどり着くこともできます。

s :: (a -> b -> c) -> (a -> b) -> a -> c
s x y z = x z (y z)

k :: a -> b -> a
k x y = x  -- aka const

i :: a -> a
i = s k k -- aka id

c :: (a -> b -> c) -> b -> a -> c
c = s (b b s) (k k) -- aka flip

b :: (b -> c) -> (a -> b) -> a -> c
b = s (k s) k  -- aka (.)

m :: (a -> b) -> b
m = s i (unsafeCoerce i)

l :: (a -> b) -> (c -> a) -> b
l = c b m

y :: (a -> a) -> a
y = s l l

(ものまね鳥) M が自己言及するので、unsafeCoerce で型をごまかしています。(ちなみに、y = b m l でも OK)

型推論をごまかさない Y コンビネータ

型推論をごまかさなくても、newtype を使って型を定義し、inline 化をプラグマで止めれば、Y コンビネータが実装できます。上記のコードの m 以降を以下で置き換えて下さい。

newtype I a = I (I a -> a)

unI :: I a -> I a -> a
unI (I x) = x
{-# NOINLINE unI #-}

m :: I a -> a
m = s unI i

l :: (a -> b) -> I a -> b
l = c b m

y :: (a -> a) -> a
y = s l (b I l) -- s l (I . l)

ちゃんと動きますね。

y fact 424

この方法は、Oleg さんに教えていただきました。ありがとうございます。

おまけ

Sxyz = xz(yz)     -- <*>
Kxy = x           -- const
Ix = x            -- id,   I = SKK
Bxyz = x(yz)      -- (.),  B = S(KS)K
Cxyz = xzy        -- flip, C = S(BBS)(KK) 
Txy = yx          --       T = CI
Wxy = xyy         --       W = SS(KI) = ST
Mx = xx           --       M = SII = STT = WI = WT
Lxy = x(yy)       --       L = CBM = BWB = QM
Qxyz = y(xz)      --       Q = CB
Oxy = y(xy)       --       O = SI
Uxy = y(xxy)      --       U = LO
Yx = x(Yx)        --       Y = SLL = BML = UU