在为特定的计算生物学文件格式编写解析器时,我遇到了一些麻烦。
这是我的代码:
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'
最佳答案
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
正如您所看到的,该函数应该获取一个封装在仿函数中的函数作为第一个参数,然后使用第二个参数中封装在仿函数中的值来应用该函数(因此 >应用仿函数)。当你给它 p_int
作为函数开头的第一个参数,它是 Parser Int
而不是Parser (a -> b)
,因此不检查类型。
事实上,他们无法检查目标是否符合你的推理;你想要betaLine
成为Parser (Int -> Int -> Int -> Int -> Direction -> ExposureList)
,但这对你有什么帮助呢?你得到一个需要 4 Int
的函数s,a 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
仿函数,然后使用应用仿函数接口(interface)将各个参数应用于它。 <$>
函数有这种类型:
(<$>) :: 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))))
如您所见,<$>
是什么?这里要做的是将函数作为左参数,将包装在仿函数中的值作为右参数,并将包装的参数应用于函数。
作为一个更简单的示例,如果您有 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
关于haskell - 秒差距类型的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10726708/