将函数分配给monad中的变量时丢失多态性

Xil*_*xio 3 polymorphism haskell

背景

我正在尝试为数值创建一个Parsec解析器.这些值可以是IntegerDouble.它们也可以是签名或未签名的.我创建了一个符号解析器,它返回idfor +negatefor的多态函数-.当给定多态符号函数时,我还有一个构造正确版本的表达式节点的函数Either Integer Double.我正在尝试将它们放在一起,如下面的代码的简化版本所示.

{-# LANGUAGE RankNTypes #-}

-- (...)

data Expr = IntExpr Integer | DoubleExpr Double
pSign :: Num a => MyParser (a -> a) -- returns id for + or negate for -
pReal :: (forall a. Num a => a -> a) -> Either Integer Double -> MyParser Expr

pNum :: MyParser Expr
pNum = do
    sign <- pSign
    numVal <- ParsecToken.naturalOrFloat lexer
    pReal sign numVal
Run Code Online (Sandbox Code Playgroud)

当我使用上面的代码时,我收到编译错误"无法推断(a~Integer)...".

当我通过let在我的monad中的语句中定义符号函数来更改我的代码时,一切都编译得很好:

pNum :: MyParser Expr
pNum = do
    sign <- pSign
    numVal <- ParsecToken.naturalOrFloat lexer
    let t = sign 1
        sign' :: Num a => a -> a
        sign' = if t == 1 then id else negate
    pReal sign numVal
Run Code Online (Sandbox Code Playgroud)

我的猜测是,在第一种情况下,多态性类型以sign某种方式丢失并转换为Integer -> Integer.

  • 什么机制使原始sign :: Num a => a -> amonad变量不能作为第一个参数pReal :: (forall a. Num a => a -> a) -> ...,而它的相同类型(sign')的重新定义版本有效?
  • 如何sign在我的monad中创建传递多态变量来实现pReal而不在我的monad中重新定义(sign')它?

笔记

我已经尝试过类似明确定义的类型的方法sign

pNum = pSign >>= \(sign :: Num a => a -> a) -> do ...
Run Code Online (Sandbox Code Playgroud)

或与正常功能定义等相同

请注意,我知道只需返回一个布尔变量sign而不是多态函数,我就可以简化代码.这个问题的关键是要了解类型在这里是如何工作的.

Dan*_*ner 9

你写

pSign :: Num a => MyParser (a -> a)
Run Code Online (Sandbox Code Playgroud)

这意味着这pSign是一个多态值,对于任何给定的实例化a,它产生一个包含单态函数的解析器.相反,您需要一个包含多态函数的单态解析器,因此:

pSign :: MyParser (forall a. Num a => a -> a)
Run Code Online (Sandbox Code Playgroud)

您还需要进行一些其他更改,以使GHC了解如何在最后一秒之前保持多态性.这是一个完整的,可编译的例子.

{-# LANGUAGE ImpredicativeTypes, LiberalTypeSynonyms, RankNTypes, ScopedTypeVariables #-}

import Text.ParserCombinators.Parsec

type MyParser = Parser

data Expr = IntExpr Integer | DoubleExpr Double
pSign :: MyParser (forall a. Num a => a -> a) -- returns id for + or negate for -
pReal :: (forall a. Num a => a -> a) -> Either Integer Double -> MyParser Expr
foo :: MyParser (Either Integer Double)
pSign = undefined
pReal = undefined
foo = undefined

pNum :: MyParser Expr
pNum =
    pSign >>= \(sign :: forall a. Num a => a -> a) ->
    foo >>= \numVal ->
    pReal sign numVal
Run Code Online (Sandbox Code Playgroud)

  • 此外,在这里使用newtypes会很有用:`newtype NumOp = NumOp(Num a => a - > a)``pSign :: MyParser NumOp``pReal :: NumOp - >整数双 - > MyParser Expr `pNum = pSign >> = \(NumOp sign) - > ...` - 少输入,然后不需要ImpredicativeTypes! (4认同)