newtype Parser a = Parser { runParser :: String -> Maybe (a, String) }
first :: (a -> b) -> (a, c) -> (b, c)
first f (a, c) = (f a, c)
inParser f = Parser . f . runParser
-- My solution
instance Functor Parser where
fmap g (Parser f) = Parser (\xs -> fmap (first g) (f xs))
-- Second solution
instance Functor Parser where
fmap = inParser . fmap . fmap . first
上面有两个fmap定义,它们是如何相同的?
您能解释第二种解决方案的工作原理吗?
这里问的同样问题:Implementing Parser Functor
答案 0 :(得分:3)
您的解决方案适用于fmap
的{{1}}实例,我认为您理解正确吗?
第二个很难得到 - 所以也许值得重新引入一些点和Maybe
.. (
?
首先让我们为)
添加签名:
inParser
你可以看到这真的只是解开/包装问题的内核;) - 使用它你可以集中精力处理inParser :: ((String -> Maybe (a, String)) ->
(String -> Maybe(b, String)))
-> Parser a -> Parser b
其他部分(String -> Maybe (a,String)
,fmap
)出于同样的原因:他们展开的东西,直到你可以到first
行动它:
a
记住fmap :: (a -> b) -> Parser a -> Parser b
fmap f p
{ def }
= inParser (fmap . fmap $ first f) $ p
{ def inParser }
= Parser . (fmap . fmap $ first f) . runParser $ p
需要runParser p
并返回String
Maybe (a, String)
是first f
或(a,c) -> (b,c)
,第一个(a,String) -> (b,String)
使用fmap
- 仿函数实例将其转换为Maybe
}。
下一个Maybe (a,String) -> Maybe (b,String)
会使用fmap
仿函数实例将String -> Maybe (a, String)
翻译为String -> Maybe (b, String)
。
最后(->) String
再次将它包起来。
我希望这有点帮助。
话虽如此,你的版本可以说更具可读性,而第二个是一个很好的脑筋急转弯,似乎更简洁/ Haskelly我更喜欢第一个版本;)
答案 1 :(得分:1)
解密无点代码的有用方法,例如
fmap = inParser . fmap . fmap . first
要忘记代码实际在做什么,只看一下类型。
我们从类型
的函数开始fun :: a -> b
假设这是我们定义的fmap
操作的参数。我们应用first
,将其类型更改为
first $ fun :: (a, String) -> (b, String)
上面,我通过查看解析器的类型猜测第二个组件是String
。然后我们应用fmap
...因为解析器的定义中有Maybe
,我们猜测必须在该类型中完成:
fmap . first $ fun :: Maybe (a, String) -> Maybe (b, String)
另一个fmap
。现在我们猜它必须在(->) String
仿函数:
fmap . fmap . first $ fun :: (String -> Maybe (a, String)) ->
(String -> Maybe (b, String))
最后,inParser
包含在newtype
:
inParser . fmap . fmap . first $ fun :: Parser a -> Parser b
因此:
inParser . fmap . fmap . first :: (a -> b) -> Parser a -> Parser b
瞧。
注意我们需要通过查看解析器的类型来猜测一些事情。这使得代码难以阅读,除非您知道什么是最终目的地类型。
为此,我不建议在这种情况下使用无点样式。