9

最近 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某些文字隐含。

4

2 回答 2

4

问题是您的类型同义词是多态类型

type s :~> s' = forall r. s -> (s' -> r) -> r

使用多态类型作为类型构造函数的参数而不是->称为“不可预测性”。例如,以下将是一个禁止使用

Maybe (forall a. a -> a)

由于各种原因,具有不可预测性的类型推断很难,这就是 GHC 抱怨的原因。(“impredicative”这个名字来源于逻辑和 Curry-Howards 同构。)


在您的情况下,解决方案只是将代数数据类型与构造函数一起使用:

data s :~> s' = StackArr { runStackArr :: forall r. s -> (s' -> r) -> r}

基本上,显式构造函数StackArr为类型检查器提供了足够的提示。

或者,您可以尝试ImpredicativeTypes语言扩展。

于 2012-02-18T13:14:37.830 回答
3

你的:~>类型不是你真正想要的(因此是ImpredicativeTypes)。如果您只是从中删除类型注释,call那么您的最后一个示例将按预期工作。另一种使其工作的方法是使用不那么花哨但更合适的类型和额外的参数:

type Tran s s' r = s -> (s' -> r) -> r

call :: Tran (s :> (Tran s s' r)) s' r
call (a :> f) = f a

但是,如果你追求的是一个很好的 DSL 语法并且你可以容忍OverlappingInstances,那么你甚至可以摆脱 liftSx 函数:

{-# LANGUAGE TypeOperators, MultiParamTypeClasses, TypeFamilies,
             FlexibleInstances, FlexibleContexts,
             UndecidableInstances, IncoherentInstances  #-}

data a :> b = a :> b deriving Show
infixl 4 :>


class Stackable s o r where
    eval :: s -> o -> r


data End = End

instance (r1 ~ s) => Stackable s End r1 where
    eval s End = s


instance (Stackable (s :> a) o r0, r ~ (o -> r0)) => Stackable s a r where
    eval s a = eval (s :> a)

instance (a ~ b, Stackable s c r0, r ~ r0) => Stackable (s :> a) (b -> c) r where
    eval (s :> a) f = eval s (f a)

-- Wrap in Box a function which should be just placed on stack without immediate application
data Box a = Box a

instance (Stackable (s :> a) o r0, r ~ (o -> r0)) => Stackable s (Box a) r where
    eval s (Box a) = eval (s :> a)


runS :: (Stackable () a r) => a -> r
runS a = eval () a

-- tests
t1 = runS 1 negate End
t2 = runS 2 1 negate (+) End

t3 = runS 1 (Box negate) ($) End
t4 = runS [1..5] 0 (Box (+)) foldr End
t5 = runS not True (flip ($)) End

t6 = runS 1 (+) 2 (flip ($)) End
于 2012-02-23T14:26:55.170 回答