Haskell 型レベル計算で継続渡し

やってみた
訂正: id:athos くんの指摘により。

やりたいこと

木を深さ優先探索して n 番目の要素を取得する。 関数型言語でならこんな風に継続渡し形式(CPS)で書けばよい:

data Tree a = Tree a :*: Tree a | Leaf a

nth :: Int -> Tree a -> a
nth n t = nth' n t (\_ -> error "no more elements")
  where
    nth' :: Int -> Tree a -> (Int -> a) -> a
    nth' 0 (Leaf a) k = a
    nth' n (Leaf _) k = k (n-1)
    nth' n (l :*: r) k = nth' n l (\i -> nth' i r k)
テスト
*Main> nth 0 (Leaf "a")
"a"
*Main> nth 1 (Leaf "a")
"*** Exception: no more elements
*Main> nth 3 ((Leaf "a" :*: Leaf "b") :*: (Leaf "c" :*: Leaf "d"))
"d"
*Main> nth 2 ((Leaf "a" :*: (Leaf "a1" :*: Leaf "b") :*: (Leaf "c" :*: Leaf "d")))
"b"
*Main> nth 5 ((Leaf "a" :*: (Leaf "a1" :*: Leaf "b") :*: (Leaf "c" :*: Leaf "d")))
"*** Exception: no more elements

型レベル計算でやってみよう

以下でみるように補助的な型クラスApplyを使えば関数渡しができるのでもちろん継続渡しも書ける。
以前 Olegさんに教えてもらった。 彼はなんでも知っている…

-- 自然数
data Z    -- ゼロ
data S n  -- n + 1

-- 木
data L u      -- 葉
data u :|: u' -- 木

-- 型レベル関数渡しのための定義
data Err      -- エラー
data NthT t f -- Nthを呼ぶ

class Fail e -- エラーメッセージ用 - インスタンスは定義しない

-- 型レベル関数のふるまいを定義するクラス
class Apply f u u' | f u -> u'

-- Err関数は NoMoreElements エラーを返す
data NoMoreElements
instance Fail NoMoreElements => Apply Err u u

-- NthT関数は 再帰的に Nth'を呼ぶ
instance Nth' i r k u => Apply (NthT r k) i u

-- Nth関数 - t を深さ優先で探索し i番目の要素を取得
class Nth i t u | i t -> u
instance Nth' i t Err u => Nth i t u

-- Nth'関数 - Nthの本体;継続渡し
class Nth' i t k u | i t k -> u
instance Nth' Z     (L u)     k u      -- n=0 のとき その要素を返す (継続は捨てる)
instance Apply k n u' =>
         Nth' (S n) (L u)     k u'     -- n>0 かつ 葉のとき n-1で継続をよぶ
instance Nth' n l (NthT r k) u'       -- n>0 かつ 木のとき 左->右と探索する
      => Nth' n     (l :|: r) k u'    -- 継続として 右でNth'を呼ぶ NthTを渡す


-- テスト用
nth_type :: Nth i t u => i -> t -> u
nth_type = undefined

-- 型レベル自然数
zero :: Z
zero = undefined
one :: S Z
one = undefined
two :: S (S Z)
two = undefined

-- 木 その1
t1 :: L Int
t1 = undefined

-- 木 その2
t2 :: (L Int :|: L Bool) :|: L Float
t2 = undefined
テスト
*Main> :t nth_type zero t1
nth_type zero t1 :: Int
*Main> :t nth_type zero t2
nth_type zero t2 :: Int
*Main> :t nth_type one t2
nth_type one t2 :: Bool
*Main> :t nth_type two t2
nth_type two t2 :: Float
*Main> :t nth_type two t2
nth_type two t2 :: Float

あってるね。

ちなみにエラーはこんな風に出る:

*Main> :t nth_type one t1 

<interactive>:1:0:
    No instance for (Fail NoMoreElements)
      arising from a use of `nth_type' at <interactive>:1:0-14
    Possible fix: add an instance declaration for (Fail NoMoreElements)

これは Olegさんの HList の論文に書いてあったテクニック。