我目前正在努力理解应用函子。我的理解在某个地方有所欠缺,但我不能准确指出是哪里。Source from where我正在努力建立理解CIS 194 UPenn-Homework 10
我正在使用一个自定义数据类型及其应用示例,如下所示:
-- A parser for a value of type a is a function which takes a String
-- represnting the input to be parsed, and succeeds or fails; if it
-- succeeds, it returns the parsed value along with the remainder of
-- the input.
newtype Parser a = Parser { runParser :: String -> Maybe (a, String) }
instance Functor Parser where
fmap :: (a -> b) -> Parser a -> Parser b
fmap fn parserA = Parser (fmap (first fn) . parseAFunc)
where parseAFunc = runParser parserA
appliedFunc p1 p2 str = case runParser p1 str of
Nothing -> Nothing
Just (f, str2) -> case runParser p2 str2 of
Nothing -> Nothing
Just (x, str3) -> Just (f x, str3)
instance Applicative Parser where
pure a = Parser (\s -> Just (a, s))
p1 <*> p2 = Parser (appliedFunc p1 p2)
-- For example, 'satisfy' takes a predicate on Char, and constructs a
-- parser which succeeds only if it sees a Char that satisfies the
-- predicate (which it then returns). If it encounters a Char that
-- does not satisfy the predicate (or an empty input), it fails.
satisfy :: (Char -> Bool) -> Parser Char
satisfy p = Parser f
where
f [] = Nothing -- fail on the empty input
f (x:xs) -- check if x satisfies the predicate
-- if so, return x along with the remainder
-- of the input (that is, xs)
| p x = Just (x, xs)
| otherwise = Nothing -- otherwise, fail
-- Using satisfy, we can define the parser 'char c' which expects to
-- see exactly the character c, and fails otherwise.
char :: Char -> Parser Char
char c = satisfy (== c)
-- Below is a parser for positive Ints which parses
-- the prefix of contiguous digits in a given String
-- as an Int
posUse :: Parser Int
posUse = Parser f
where
f xs
| null ns = Nothing
| otherwise = Just (read ns, rest)
where (ns, rest) = span isDigit xs
-- Below function takes a function and a pair and returns a pair
-- with the function applied to the first element of pair
first :: (a -> b) -> (a, c) -> (b, c)
first fn (x, y) = (fn x, y)
使用上面的所有设置,我尝试使用上面定义的简单解析器来构造一些更复杂的解析器。所以fmap的类型是fmap :: Functor f => (a -> b) -> f a -> f b
。根据我目前的理解,Applicationative允许我们将fmap
扩展为具有n元函数和n个函子参数的函数,并返回结果函子。fmap
可以根据pure
和<*>
定义为:
fmap1 :: Applicative f => (a -> b) -> f a -> f b
fmap1 f x = pure f <*> x
我理解为什么上面的代码可以正常工作,因为pure
和<*>
的实现是合适的,因为这些类型很好地排列在一起。
将其扩展到fmap2
(即liftA2
):
fmap2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
fmap2 f x y = pure f <*> x <*> y
我对上述工作原理的理解如下:pure g
的类型是f (a -> b -> c)
,所以pure g <*> x
的类型是f (b -> c)
,因为函数是curried的,然后这个类型和y(f b
)的类型结合,使用<*>
,最终给予的结果的类型是f c
,这就是我们需要的fmap2
的类型。
当我试图用2个简单的解析器构建一个解析器时,这种理解并没有被打破,如下所示:
-- This Parser expects to see the characters ’a’ and ’b’ and returns them
-- as a pair.
abParser = liftA2 (\c1 c2 -> (c2 c1)) (char 'a') (char 'b')
上面的工作和预期的一样。
但是,当我试图使解析器intPair
读取两个由空格分隔的整数值,并使用liftA3
返回列表中的整数值时,我对lift的理解不起作用,因为我希望下面的代码起作用,但liter抱怨:
intPair :: Parser [Int]
intPair = liftA3 (\x y z -> [z x]) posUse (char ' ') posUse
上面的代码无法编译,因为最后一个参数需要具有Parser (Int -> a)
类型(根据类型推断),但我传递了posUse
,它具有Parser Int
类型。
TL;DR:抱歉描述太长了。如果有人不想经历这一切(特别是自定义数据类型及其应用程序和功能函数示例)--请让我知道我对fmap2
(又称liftA2
)工作原理的理解是否正确,以及这种理解如何扩展到liftA3
?liftA3
的定义似乎与使用<*>
和pure
的liftA2
的定义的扩展不同。
编辑1:如上所述,下面的行被投诉的短绒
intPair :: Parser [Int]
intPair = liftA3 (\x y z -> [z x]) posUse (char ' ') posUse
liter期望最后一个参数的类型是Parser (Int -> a)
,但是在我定义了一个显式函数而不是传递lambda之后,它就像我期望的那样工作了。
fnn :: Int -> Char -> Int -> [Int]
fnn arg1 arg2 arg3 = [arg1, arg3]
intPair = liftA3 fnn posUse (char ' ') posUse
它的工作原理和我预期的一样。
2条答案
按热度按时间bvjxkvbb1#
是的,您对
liftA2
的理解是正确的,而liftA3
只是更相似而已。我猜不出为什么它看起来不同,但是将liftA2
的定义内嵌到liftA3
的定义中可能会有所帮助。我从https://hackage.haskell.org/package/base-4.17.0.0/docs/src/GHC.Base.html中提取了这些定义,并稍微重新排列了
liftA2
的定义,使其更加明确--它与您的fmap2
完全匹配。现在,请注意
liftA3
包含了对liftA2 f a b
的调用。我们知道liftA2 f a b
是如何定义的,并且由于纯粹性,我们总是可以内联定义以生成语义相同的术语。因此,让我们这样做:括号是多余的,但为了以防万一,我在替换时把它们加了进去。现在,如果我们去掉它们,我们会发现这里没有新的想法:
我们提升这个函数,得到
f (a -> b -> c -> d)
,然后把它应用到a
,得到<*>
,以此类推,直到最后得到f d
。ccrfmcuu2#
在将命名函数转换为匿名函数时,您犯了一个愚蠢的错误。对比: