Haskell中的行多态性:难以用“";“转变”;

Haskell中的行多态性:难以用“";“转变”;,haskell,polymorphism,higher-order-functions,concatenative-language,impredicativetypes,Haskell,Polymorphism,Higher Order Functions,Concatenative Language,Impredicativetypes,我受到最近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 represent

我受到最近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
调用
应该将
(s:>(s:~>s'))
形式的堆栈转换为
s
,基本上是通过将转换(保存在堆栈顶部)应用于其“其余”部分。我想它应该是这样工作的:

ghci> runF $ start _1 (liftS0 neg) call
-1
但实际上,它给了我一个巨大的类型不匹配错误。我做错了什么?“堆栈转换”表示法能否充分处理高阶函数,或者我是否需要调整它

1N.B。与这些人的做法不同,我希望它是
runF$start(push 1)(push 2)add
,而不是
start push 1 push 2 add end
,这个想法是,也许以后我可以使用一些类型类魔法,使
push
对某些文本隐式


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

type s :~> s' = forall r. s -> (s' -> r) -> r
使用多态类型作为类型构造函数(而不是
->
的参数称为“不确定性”。例如,以下是一种非指示性用途

Maybe (forall a. a -> a)
由于各种原因,具有不确定性的类型推断很困难,这就是GHC抱怨的原因。(名称“非指示性”来自逻辑和Curry Howards同构。)


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

data s :~> s' = StackArr { runStackArr :: forall r. s -> (s' -> r) -> r}
基本上,显式构造函数
StackArr
为类型检查器提供了足够的提示


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

您的
:~>
类型不是您真正想要的类型(因此
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语法,并且您可以容忍重叠实例,那么您甚至可以基本上摆脱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

实际上,我也希望摆脱
start
,只需要
runF$\u 1\u 1 add
,尽管我不太明白这种设置是如何实现的,不仅仅是函数类型。使用数据声明的问题在于它污染了所需的语法。类型同义词工作良好的原因是转换是一个函数,并且这些函数可以通过函数应用程序链接在一起,就像它是函数组合一样。我确实尝试了
ImpredicativeTypes
,这使得
调用
可以很好地键入,但是按照需要使用
调用
是不可能的;我给它的类型本身就有问题。我认为问题在于类型检查器无法理解类型级别上的“函数应用程序”。