在编写特定计算生物学文件格式的解析器时,我遇到了一些麻烦。
这是我的代码:
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'
答案 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,Direction
和ExposureList
的函数,并且当你将该函数赋给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
构造函数,它将类型a
和b
转换为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