parsec类型的问题

时间:2012-05-23 19:40:08

标签: haskell parsec

在编写特定计算生物学文件格式的解析器时,我遇到了一些麻烦。

这是我的代码:

betaLine = string "BETA " *> p_int <*> p_int  <*> p_int <*> p_int <*> p_direction <*> p_exposure <* eol

p_int = liftA (read :: String -> Int) (many (char ' ') *> many1 digit <* many (char ' '))

p_direction = liftA mkDirection (many (char ' ') *> dir <* many (char ' '))
            where dir = oneStringOf [ "1", "-1" ]

p_exposure = liftA (map mkExposure) (many (char ' ') *> many1 (oneOf "io") <* many (char ' '))

现在,如果我注释掉betaLine的定义,那么所有内容都会编译,并且我已经成功测试了各个解析器p_int,p_direction和p_exposure。

但是,当存在betaLine等式时,我得到一个我不太明白的类型错误。我对应用的理解是&lt; *&gt;错误?最终,我希望这返回Int - &gt; Int - &gt; Int - &gt; Int - &gt;方向 - &gt; ExposureList,我将能够为BetaPair的构造函数提供。

类型错误是:

Couldn't match expected type `a5 -> a4 -> a3 -> a2 -> a1 -> a0'
            with actual type `Int'
Expected type: Text.Parsec.Prim.ParsecT
                 s0 u0 m0 (a5 -> a4 -> a3 -> a2 -> a1 -> a0)
  Actual type: Text.Parsec.Prim.ParsecT s0 u0 m0 Int
In the second argument of `(*>)', namely `p_int'
In the first argument of `(<*>)', namely `string "BETA " *> p_int'

1 个答案:

答案 0 :(得分:5)

tl; dr :你想要这个表达式:

betaLine = string "BETA " *> (BetaPair <$> p_int <*> p_int  <*> p_int <*> p_int <*> p_direction <*> p_exposure) <* eol

在下面阅读为什么


再一次,这部分是优先问题。你现在的行是什么:

string "BETA " *> p_int <*> p_int ...

...就是它创建了一个这样的解析器:

(string "BETA " *> p_int) <*> (p_int) ...

这不是主要问题,事实上,如果解析器的其余部分是正确的,那么上面的语义错误的解析器仍会产生正确的结果。但是,正如您所说,您对<*>的工作原理有轻微的误解。它的签名是:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

正如你所看到的,该函数应该将一个包含在仿函数中的函数作为第一个参数,然后使用第二个参数中的仿函数包含的值应用(因此 applicative 仿函数)。当您将p_int作为函数开头的第一个参数时,它是Parser Int而不是Parser (a -> b),因此类型不会检查。

事实上,如果目标是你用你的推理所说的那样,他们就不能进行打字检查;您希望betaLine成为Parser (Int -> Int -> Int -> Int -> Direction -> ExposureList),但这对您有何帮助?你得到一个需要4 Int s,DirectionExposureList的函数,并且当你将该函数赋给BetaPair的构造函数时,它可以神奇地构造一个BetaPair吗?请记住,函数与右侧相关联,因此如果BetaPair构造函数具有类型:

Int -> Int -> Int -> Int -> Direction -> ExposureList -> BetaPair

......这并不意味着:

(Int -> Int -> Int -> Int -> Direction -> ExposureList) -> BetaPair

它实际上意味着:

Int -> (Int -> (Int -> (Int -> (Direction -> (ExposureList -> BetaPair)))))

您可以将betaLine设为Parser BetaPair,这样会更有意义。您可以使用<$>运算符,它是fmap的同义词(在函数箭头下),可让您将BetaPair构造函数提升到Parser仿函数,然后使用applicative functor接口将单个参数应用于它。 <$>函数具有以下类型:

(<$>) :: Functor f => (a -> b) -> f a -> f b

在这种情况下,您提出的第一个参数是BetaPair构造函数,它将类型ab转换为BetaPair的类型组件“功能”,产生这个特定的签名:

(<$>) :: (Int -> (Int -> (Int -> (Int -> (Direction -> (ExposureList -> BetaPair)))))) 
      -> f Int -> f (Int -> (Int -> (Direction -> (ExposureList -> BetaPair))))

正如您所看到的,<$>将在此处执行的操作是将函数作为左参数,将函数包装在函数中作为右参数,并将包装参数应用于函数。 / p>

作为一个更简单的示例,如果您有f :: Int -> String,则使用以下表达式:

f <$> p_int

...将解析一个整数,使用该整数作为参数应用函数f,并将结果包装在仿函数中,因此上面的表达式具有类型Parser String。此职位的<$>类型为:

(<$>) :: (Int -> String) -> Parser Int -> Parser String

因此,使用<$>将第一个参数应用于构造函数。那么你如何处理其他论点呢?好吧,这就是<*>的用武之地,我认为你从类型签名中理解它的作用:如果你链接它的用途,它将连续再将一个参数应用于函数包含的函数中左边,将编码器展开到右边。所以,再一个简单的例子;假设你有一个函数g :: Int -> Int -> String和以下表达式:

g <$> p_int <*> p_int

g <$> p_int表达式会将p_int的结果应用于g的第一个参数,因此该表达式的类型为Parser (Int -> String)<*>然后应用下一个参数,特定类型<*>为:

(<*>) :: Parser (Int -> String) -> Parser Int -> Parser String

因此,上面整个表达式的类型是Parser String

同样地,对于您的情况,在这种情况下,您可以让BetaPair成为您的g,从而产生这种模式:

BetaPair <$> one <*> parser <*> per <*> argument <*> to <*> betaPair

如上所述,生成的解析器因此是:

betaLine = string "BETA " *> (BetaPair <$> p_int <*> p_int  <*> p_int <*> p_int <*> p_direction <*> p_exposure) <* eol