Haskell 秩-2型的弱化约束
从a继续,我有一个函数 量化函数作为参数,如下所示:Haskell 秩-2型的弱化约束,haskell,Haskell,从a继续,我有一个函数 量化函数作为参数,如下所示: {-# LANGUAGE RankNTypes #-} 对于不需要额外约束的函数,可以很好地使用,例如: emap :: (forall a. Expression a -> Expression a) -> Expression b -> Expression b 但是,我现在想将这个函数与一个需要额外 约束,但以下不进行类型检查 postmap :: (forall a. Expression a -> Exp
{-# LANGUAGE RankNTypes #-}
对于不需要额外约束的函数,可以很好地使用,例如:
emap :: (forall a. Expression a -> Expression a) -> Expression b -> Expression b
但是,我现在想将这个函数与一个需要额外
约束,但以下不进行类型检查
postmap :: (forall a. Expression a -> Expression a) -> Expression b -> Expression b
postmap f = f . emap (postmap f)
reduce = postmap step
因此,我似乎需要以某种方式“削弱”或“专门化”所有对
forall a。打包一个
约束。似乎没有必要将约束添加到
emap
本身的签名,但我看不到任何其他方法来解决这个问题
我不知道如何解决这个问题,但我可以看到一些可用的选项
bind
的签名周围添加约束,这样就可以轻松地
类型已检查emap'
,并根据
emap
或反之亦然(emap
是一个枯燥的机械定义,它不会
有两个相同的函数体(仅在签名上不同)假设您重写了所有内容,使得
type T=forall a。表达式a->表达式a
和postmap':T->T
。我认为应该清楚的是,postmap
的类型是相同的。(您可以尝试>:t[postmap',postmap]
)。
给定代码< >代替品::打包a= >标识符> a>表达式a>表达式a < /> >,考虑您对“代码>绑定< /COD>”的定义:您将一个参数传递给<代码> PASMAP> /代码>,期望值类型<代码> T < /代码>;但是您已经给了它一个类型为Expression a->Expression a
的值。小心!这些类型不一样,因为在后一种情况下,所有a的位于左侧某处postmap
需要一个函数,它可以选择a
,但是如果其他人已经选择了a
,它已经被赋予了一个函数。假设您的示例是进行类型检查,然后使用a~()
调用bind
。赋予postmap
的函数类型必须是Expression()->Expression()
,但这显然是胡说八道
如果我不是很清楚,请考虑“工作”版本:
需要注意的重要一点是:无论您是按照上面的方式定义,还是为所有a定义type T=。包a=>…
,错误相同。所以你的问题与你想象的不同
调试这类问题的简单方法是使用
type T = forall a . Expression a -> Expression a
postmap :: T -> T
postmap = undefined
-- These two substitutes have different types!
substitute :: Pack a => Identifier -> a -> Expression a -> Expression a
substitute = undefined
substitute' :: Pack a => Identifier -> a -> T
substitute' = undefined
-- Doesn't compile
bind :: Pack a => Identifier -> a -> Expression a -> Expression a
bind ident value = postmap (substitute ident value)
-- Does compile!
bind' :: Pack a => Identifier -> a -> T
bind' ident value = postmap (substitute' ident value)
错误通常更清楚(尽管在这种情况下不是这样)。很抱歉,我不能给出一个真正的解决方案,因为我不知道这些函数实际上在做什么。但我怀疑#3是您的答案。首先,让我们用足够的代码充实您的示例LiteralToken
只是用来表示用什么替换变量
newtype T = T (forall a . Expression a -> Expression a)
现在,我们可以实现替换标识符。要在emap
或postmap
中使用,substitute
需要能够对表达式
树的所有部分进行操作,而不管它是否与被替换的变量的类型相同。这就是为什么表达式
s的类型变量与值的类型变量不同(这也是原因所在)
现在我们可以尝试一些例子:
substitute :: Pack a => Identifier -> a -> Expression b -> Expression b
substitute ident value e = case e of
Var id | id == ident -> Lit (pack value)
otherwise -> e
如果标识符的类型错误,则无法得到所需的标识符,它将被替换。这是因为substitute
无法检查所替换变量的类型。在下面的示例中,两个Var“x”
s都替换为为Int
生成的文本,即使第二个变量用于Bool
example1 :: Expression (Int, Bool)
example1 = Tuple (Var "x") (Var "y")
print . bind "x" (7 :: Int) $ example1
Tuple (Lit LiteralToken) (Var "y")
类型安全
如果我们在LiteralToken
中添加一个类型变量,那么它在某种程度上取决于它所表示的类型
example2 :: Expression (Int, Bool)
example2 = Tuple (Var "x") (Var "x")
print . bind "x" (7 :: Int) $ example2
Tuple (Lit LiteralToken) (Lit LiteralToken)
我们以前的替换
将出现编译器错误
data LiteralToken a = LiteralToken
deriving Show
class Pack a where
pack :: a -> LiteralToken a
data Expression a where
Var :: Identifier -> Expression a
Lit :: LiteralToken a -> Expression a
Tuple :: Expression a -> Expression b -> Expression (a, b)
substitute
需要某种方法来检查它所操作的类型是否正确<代码>数据。可键入
解决了此问题。要求由变量表示的任何术语具有运行时可识别的类型似乎并不不合理,因此将此约束添加到表达式
树本身是合理的。或者,我们可以向表达式树添加一个类型注释,该注释可以证明任何术语都是特定类型的。我们将沿着第二条路线走。这将需要一些进口
Could not deduce (b ~ a)
...
In the first argument of `pack', namely `value'
In the first argument of `Lit', namely `(pack value)'
In the expression: Lit (pack value)
下面是扩展为包含类型注释的表达式树
import Data.Typeable
import Data.Maybe
bind
和substitute
现在只能处理可键入的
变量。替换缺少类型注释的变量没有任何作用,变量保持不变
data Expression a where
Var :: Identifier -> Expression a
Lit :: LiteralToken a -> Expression a
Tuple :: Expression a -> Expression b -> Expression (a, b)
Typed :: Typeable a => Expression a -> Expression a
emap :: (forall a. Expression a -> Expression a) -> Expression b -> Expression b
emap f e = case e of
Tuple a b -> Tuple (f a) (f b)
Typed a -> Typed (f a)
otherwise -> e
substitute
中的所有类型检查和铸造工作都由gcast
完成<代码>可从函数签名键入a
,证明由Lit生成的LiteralToken a
中的a
。pack$value
具有可键入的实例。案例中的Typed
构造函数提供了输出表达式的typeb
也有一个Typeable
实例的证明。请注意,如果删除了fmap-Typed
,则代码仍然有效;它只是保留类型注释
typed :: Typeable a => Expression a -> Expression a
typed t@(Typed _) = t
typed e = Typed e
typedVar :: Typeable a => Identifier -> Expression a
typedVar = Typed . Var
以下两个函数使添加类型注释变得容易
bind :: (Pack a, Typeable a) => Identifier -> a -> Expression b -> Expression b
bind ident value = postmap (substitute ident value)
substitute :: (Pack a, Typeable a) => Identifier -> a -> Expression b -> Expression b
substitute ident value e = case e of
Typed (Var id) | id == ident -> fromMaybe e . fmap Typed . gcast . Lit . pack $ value
otherwise -> e
我们的两个示例现在完成了我们希望它们完成的任务。尽管第二个示例中的两个变量名称相同,但这两个示例仅替换整数变量。我们使用智能typedVar
构造函数而不是Var
来构建所有变量
bind :: (Pack a, Typeable a) => Identifier -> a -> Expression b -> Expression b
bind ident value = postmap (substitute ident value)
substitute :: (Pack a, Typeable a) => Identifier -> a -> Expression b -> Expression b
substitute ident value e = case e of
Typed (Var id) | id == ident -> fromMaybe e . fmap Typed . gcast . Lit . pack $ value
otherwise -> e
typed :: Typeable a => Expression a -> Expression a
typed t@(Typed _) = t
typed e = Typed e
typedVar :: Typeable a => Identifier -> Expression a
typedVar = Typed . Var
example1 :: Expression (Int, Bool)
example1 = Tuple (typedVar "x") (typedVar "y")
print . bind "x" (7 :: Int) $ example1
Tuple (Typed (Lit LiteralToken)) (Typed (Var "y"))
example2 :: Expression (Int, Bool)
example2 = Tuple (typedVar "x") (typedVar "x")
print . bind "x" (7 :: Int) $ example2
Tuple (Typed (Lit LiteralToken)) (Typed (Var "x"))
inferType :: Expression a -> Expression a
inferType e = case e of
Typed t@(Typed _) -> t
t@(Tuple (Typed _) (Typed _)) -> typed t
otherwise -> e
inferTypes = postmap inferType
print . inferTypes $ example1
Typed (Tuple (Typed (Var "x")) (Typed (Var "y")))