4

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

这是我的代码:

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 方程时,我会收到一个我不太理解的类型错误。我对应用程序 <*> 的理解是错误的吗?最终,我希望它返回 Int -> Int -> Int -> Int -> Direction -> 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'
4

1 回答 1

5

tl;博士:你想要这个表达:

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

如您所见,该函数应该获得一个包装在函子中的函数作为第一个参数,然后使用包装在第二个参数中的函子中的值应用该函数(因此是应用函子)。当您p_int在函数开头将其作为第一个参数时,它是 aParser Int而不是 a Parser (a -> b),因此不会检查类型。

事实上,它们不能用来检查目标是否是你用推理陈述的;你想betaLine成为一个Parser (Int -> Int -> Int -> Int -> Direction -> ExposureList),但这对你有什么帮助?你得到一个需要 4 Ints, a Directionand的函数ExposureList,当你将该函数提供给 a 的构造函数时BetaPair,它应该神奇地构造 aBetaPair出来吗?请记住,函数关联到右侧,因此如果BetaPair构造函数具有类型:

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

...这与以下内容不同:

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

它实际上是这个意思:

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

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

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

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

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

如您所见,<$>这里将做的是将一个函数作为左参数,并将一个包装在仿函数中的值作为右参数,并将包装的参数应用于函数。

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

f <$> p_int

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

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

因此, using<$>将第一个参数应用于您的构造函数。那么你如何处理其他争论呢?好吧,这就是<*>in 的用武之地,我认为您从类型签名中了解了它的作用:如果您将其使用链接起来,它将通过展开右边的函子。所以,再举一个更简单的例子;假设您有一个函数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
于 2012-05-23T20:14:36.637 回答