Haskell 秩-2型的弱化约束

Haskell 秩-2型的弱化约束,haskell,Haskell,从a继续,我有一个函数 量化函数作为参数,如下所示: {-# LANGUAGE RankNTypes #-} 对于不需要额外约束的函数,可以很好地使用,例如: emap :: (forall a. Expression a -> Expression a) -> Expression b -> Expression b 但是,我现在想将这个函数与一个需要额外 约束,但以下不进行类型检查 postmap :: (forall a. Expression a -> Exp

从a继续,我有一个函数 量化函数作为参数,如下所示:

{-# 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
    构造函数提供了输出表达式的type
    b
    也有一个
    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")))