私は最近のHaskellブログアクティビティ1に触発されて、HaskellでForthのようなDSLを書いてみました。私が採用したアプローチは、同時に単純で紛らわしいものです。
{-# LANGUAGE TypeOperators, RankNTypes, ImpredicativeTypes #-}
-- a :~> b represents a "stack transformation"
-- from stack type "a" to stack type "b"
-- a :> b represents a "stack" where the top element is of type "b"
-- and the "rest" of the stack has type "a"
type s :~> s' = forall r. s -> (s' -> r) -> r
data a :> b = a :> b deriving Show
infixl 4 :>
簡単なことをするために、これは非常にうまく機能します:
start :: (() -> r) -> r
start f = f ()
end :: (() :> a) -> a
end (() :> a) = a
stack x f = f x
runF s = s end
_1 = liftS0 1
neg = liftS1 negate
add = liftS2 (+)
-- aka "push"
liftS0 :: a -> (s :~> (s :> a))
liftS0 a s = stack $ s :> a
liftS1 :: (a -> b) -> ((s :> a) :~> (s :> b))
liftS1 f (s :> a) = stack $ s :> f a
liftS2 :: (a -> b -> c) -> ((s :> a :> b) :~> (s :> c))
liftS2 f (s :> a :> b) = stack $ s :> f a b
単純な関数は、対応するスタック変換に簡単に変換できます。これまでのところ、遊んでみると楽しい結果が得られます。
ghci> runF $ start _1 _1 neg add
0
これを高階関数で拡張しようとすると問題が発生します。
-- this requires ImpredicativeTypes...not really sure what that means
-- also this implementation seems way too simple to be correct
-- though it does typecheck. I arrived at this after pouring over types
-- and finally eta-reducing the (s' -> r) function argument out of the equation
-- call (a :> f) h = f a h
call :: (s :> (s :~> s')) :~> s'
call (a :> f) = f a
call
は、フォームの「残り」に変換(スタックの先端に保持されている)を本質的に「適用」することにより、(s :> (s :~> s'))
フォームのスタックをフォームに変換することになっています。s
私はそれがこのように機能するはずだと思います:
ghci> runF $ start _1 (liftS0 neg) call
-1
しかし実際には、それは私に巨大な型の不一致エラーを与えます。私は何が間違っているのですか?「スタック変換」表現は高階関数を十分に処理できますか、それとも調整する必要がありますか?
1 N.B. これらの人がそれをした方法とは異なりstart push 1 push 2 add end
、私はそれをしたいのですrunF $ start (push 1) (push 2) add
が、おそらく後で私はいくつかの型クラスの魔法を使ってpush
特定のリテラルの暗黙を作ることができるという考えです。