在编写特定计算生物学文件格式的解析器时,我遇到了一些麻烦.
这是我的代码:
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 ' '))
Run Code Online (Sandbox Code Playgroud)
现在,如果我注释掉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'
Run Code Online (Sandbox Code Playgroud)
tl; dr:你想要这个表达式:
betaLine = string "BETA " *> (BetaPair <$> p_int <*> p_int <*> p_int <*> p_int <*> p_direction <*> p_exposure) <* eol
Run Code Online (Sandbox Code Playgroud)
阅读以下原因.
再一次,这部分是优先问题.您当前的行是做什么的:
string "BETA " *> p_int <*> p_int ...
Run Code Online (Sandbox Code Playgroud)
...是它创建一个像这样的解析器:
(string "BETA " *> p_int) <*> (p_int) ...
Run Code Online (Sandbox Code Playgroud)
这不是主要问题,事实上,如果解析器的其余部分是正确的,那么上面的语义错误的解析器仍然会产生正确的结果.但是,正如你所说,你对如何<*>运作有一点误解.它的签名是:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,功能应该得到包裹在一个仿函数作为第一个参数,它则功能适用于使用包裹在仿函数的第二个参数(因此价值应用性仿函数).当你把它p_int作为函数开头的第一个参数时,它是a Parser Int而不是a Parser (a -> b),所以类型不检查.
事实上,如果目标是你用你的推理所说的那样,他们就不能进行打字检查; 你想betaLine成为一个Parser (Int -> Int -> Int -> Int -> Direction -> ExposureList),但这对你有什么帮助?你得到一个需要4 Int秒的函数,a Direction和ExposureList,当你把这个函数BetaPair赋给a的构造函数时,神奇地认为它构造了一个BetaPair吗?请记住,函数关联到右侧,因此如果BetaPair构造函数具有类型:
Int -> Int -> Int -> Int -> Direction -> ExposureList -> BetaPair
Run Code Online (Sandbox Code Playgroud)
......这并不意味着:
(Int -> Int -> Int -> Int -> Direction -> ExposureList) -> BetaPair
Run Code Online (Sandbox Code Playgroud)
它实际上意味着:
Int -> (Int -> (Int -> (Int -> (Direction -> (ExposureList -> BetaPair)))))
Run Code Online (Sandbox Code Playgroud)
你可以改为betaLine成为a Parser BetaPair,这会更有意义.您可以使用<$>运算符,它是fmap(在函数箭头下)的同义词,它允许您将BetaPair构造Parser函数提升到仿函数,然后使用applicative functor接口将单个参数应用于它.该<$>函数具有以下类型:
(<$>) :: Functor f => (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)
在这种情况下,你抬起你的第一个参数是BetaPair构造函数,变换类型a和b进入的类型组件BetaPair"功能",这产生特定的签名:
(<$>) :: (Int -> (Int -> (Int -> (Int -> (Direction -> (ExposureList -> BetaPair))))))
-> f Int -> f (Int -> (Int -> (Direction -> (ExposureList -> BetaPair))))
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,<$>这里的意思是将函数作为左参数,将函数包装在函数中作为右参数,并将包装参数应用于函数.
作为一个更简单的示例,如果您有f :: Int -> String,则使用以下表达式:
f <$> p_int
Run Code Online (Sandbox Code Playgroud)
...将解析一个整数,f将该整数作为参数应用于函数,并将结果包装在仿函数中,因此上面的表达式具有类型Parser String.<$>这个职位的类型是:
(<$>) :: (Int -> String) -> Parser Int -> Parser String
Run Code Online (Sandbox Code Playgroud)
因此,使用<$>第一个参数应用于构造函数.那么你如何处理其他论点呢?好吧,这就是它的<*>用武之地,我认为你从类型签名中理解它的作用:如果你链接它的用途,它将通过展开左边的functor中包含的函数连续再应用一个参数.向右的仿函数.所以,再一个简单的例子; 说你有一个函数g :: Int -> Int -> String和以下表达式:
g <$> p_int <*> p_int
Run Code Online (Sandbox Code Playgroud)
的g <$> p_int表达将应用结果的p_int到的第一个参数g,从而使表达的类型是Parser (Int -> String).在<*>随后应用于下一个参数,与特定类型的<*>存在:
(<*>) :: Parser (Int -> String) -> Parser Int -> Parser String
Run Code Online (Sandbox Code Playgroud)
所以,上面整个表达式的类型是Parser String.
同样地,对于你的情况,你可以在这种情况下BetaPair成为你的g,产生这种模式:
BetaPair <$> one <*> parser <*> per <*> argument <*> to <*> betaPair
Run Code Online (Sandbox Code Playgroud)
如上所述,生成的解析器因此是:
betaLine = string "BETA " *> (BetaPair <$> p_int <*> p_int <*> p_int <*> p_int <*> p_direction <*> p_exposure) <* eol
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
460 次 |
| 最近记录: |