Haskell 中的行多态性:使用“转换”编写 Forth DSL 时遇到问题
我受到最近的 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
但实际上,它给了我一个巨大类型不匹配错误。我做错了什么? “堆栈变换”表示是否足以处理高阶函数,或者我是否需要调整它?
1NB 与这些人的做法不同,我希望它是 runF $ start (push 1) (push 2)添加
,这个想法是也许稍后我可以使用一些类型类魔法来使push
对某些文字隐式。
I've been inspired by the recent Haskell blog activity1 to try my hand at writing a Forth-like DSL in Haskell. The approach I have taken is simultaneously straightforward and confusing:
{-# 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 :>
For doing simple things, this works quite nicely:
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
Simple functions can trivially be transformed into their corresponding stack transformations. Some playing around yields pleasant results so far:
ghci> runF $ start _1 _1 neg add
0
The trouble comes when I try to extend this with higher-order functions.
-- 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
is supposed to transform a stack of the form (s :> (s :~> s'))
to the form s
, by essentially "applying" the transformation (held at the tip of the stack) to the "rest" of it. I imagine it should work like this:
ghci> runF $ start _1 (liftS0 neg) call
-1
But in actuality, it gives me a huge type mismatch error. What am I doing wrong? Can the "stack transformation" representation sufficiently handle higher-order functions, or do I need to adjust it?
1N.B. Unlike how these guys did it, instead of start push 1 push 2 add end
, I want it to be runF $ start (push 1) (push 2) add
, the idea being that maybe later I can work some typeclass magic to make the push
implicit for certain literals.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
问题是您的类型同义词是多态类型
使用多态类型作为
->
之外的类型构造函数的参数称为“必然性”。例如,以下内容将是谓语用法由于各种原因,具有谓语性的类型推断很困难,这就是 GHC 抱怨的原因。 (“谓语”这个名字来自逻辑和柯里-霍华德同构。)
在您的情况下,解决方案只是使用带有构造函数的代数数据类型:
基本上,显式构造函数
StackArr
为类型检查器提供了足够的提示。或者,您可以尝试
ImpredicativeTypes
语言扩展。The problem is that your type synonym is a polymorphic type
Using a polymorphic type as an argument to a type constructor other than
->
is called "impredicativity". For instance, the following would be an impredicative useFor various reasons, type inference with impredicativity is hard, that's why GHC complains. (The name "impredicative" comes from logic and the Curry-Howards isomorphism.)
In your case, the solution is simply to use an algebraic data type with a constructor:
Basically, the explicit constructor
StackArr
supplies enough hints to the type checker.Alternatively, you can try the
ImpredicativeTypes
language extension.您的
:~>
类型不是您真正想要的类型(因此是ImpredicativeTypes
)。如果您只是从call
中删除类型注释,那么您的最后一个示例将按预期工作。让它工作的另一种方法是使用不那么花哨但更合适的类型和额外的参数:但是如果你追求的是一个很好的 DSL 语法并且你可以容忍
OverlappingInstances
那么你甚至可以得到摆脱 liftSx 功能:Your
:~>
type is not what you actually want (hence theImpredicativeTypes
). If you just remove type annotation fromcall
then your last sample will work as expected. Another way to make it work is to use less fancy but more appropriate type with extra parameter:But if what you are after is a nice DSL syntax and you are can tolerate
OverlappingInstances
then you can even pretty much get rid of liftSx functions: