最近 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'))
来将 form的堆栈转换为 form 。s
我想它应该像这样工作:
ghci> runF $ start _1 (liftS0 neg) call
-1
但实际上,它给了我一个巨大的类型不匹配错误。我究竟做错了什么?“堆栈转换”表示可以充分处理高阶函数,还是我需要调整它?
1注 与这些人的做法不同start push 1 push 2 add end
,我希望它是,而不是 ,我runF $ start (push 1) (push 2) add
的想法是也许以后我可以使用一些类型类魔法来使push
某些文字隐含。