我目前正試圖控制對 Applicative 函子的理解。我的理解在某處有所不足,但我無法確定在哪里。我試圖建立理解的來源CIS 194 UPenn -作業 10
我正在使用自定義資料型別及其 Applicative 實體,如下所示:
-- 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 有 type fmap :: Functor f => (a -> b) -> f a -> f b。根據我目前的理解,Applicative 允許我們擴展fmap到采用 n 元函式和 n 個 Functor 引數并回傳結果仿函式的函式。
fmap可以用pure和來定義<*>:
fmap1 :: Applicative f => (a -> b) -> f a -> f b
fmap1 f x = pure f <*> x
我理解為什么上述方法會起作用,因為pure和的實作<*>是適當的,因為型別很好地排列在一起。
將此擴展到fmap2aka 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),因為函式已被柯里化。然后這個型別與 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我對提升的理解回傳串列中的整數值時,它不起作用,因為我希望以下作業但 linter 抱怨:
intPair :: Parser [Int]
intPair = liftA3 (\x y z -> [z x]) posUse (char ' ') posUse
上面沒有編譯,因為最后一個引數需要具有型別Parser (Int -> a) (根據型別推斷),但我已經通過了posUse它的型別Parser Int。
TL; DR :抱歉,描述太長了。如果有人不想經歷這一切(特別是自定義資料型別及其 Applicative 和 Functor 實體)——請告訴我我對為什么fmap2akaliftA2有效的理解是否正確以及這種理解如何擴展到liftA3?的定義liftA3似乎與liftA2使用<*>和的定義的擴展有所不同pure。
編輯 1:如上所述,linter 抱怨以下行
intPair :: Parser [Int]
intPair = liftA3 (\x y z -> [z x]) posUse (char ' ') posUse
linter 期望最后一個引數的型別是Parser (Int -> a). 但是在我定義了一個顯式函式而不是傳遞一個 lambda 之后,它就可以正常作業了。
fnn :: Int -> Char -> Int -> [Int]
fnn arg1 arg2 arg3 = [arg1, arg3]
intPair = liftA3 fnn posUse (char ' ') posUse
它按我的預期作業。
uj5u.com熱心網友回復:
是的,您的理解liftA2是正確的,并且liftA3幾乎相同。我無法猜測為什么它看起來與您不同,但將定義行內liftA2到定義中liftA3可能會有所幫助。
liftA2 :: Applicative f => (a -> b -> c)
-> f a -> f b -> f c
liftA2 f x y = pure f <*> x <*> y
liftA3 :: Applicative f => (a -> b -> c -> d)
-> f a -> f b -> f c -> f d
liftA3 f a b c = liftA2 f a b <*> c
我從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定義,并且由于純度,我們總是可以行內定義以產生語意相同的術語。所以讓我們這樣做:
liftA3 f a b c = (pure f <*> a <*> b) <*> c
括號是多余的,但我在替換時將它們放入以防萬一。現在,如果我們洗掉它們,我們會發現這里沒有新想法:
liftA3 f a b c = pure f <*> a <*> b <*> c
我們提升函式 yielding f (a -> b -> c -> d),然后將其應用于awith<*>以得到 an f (b -> c -> d),依此類推,一直到f d最后的 an 。
uj5u.com熱心網友回復:
從命名函式轉換為匿名函式時,您犯了一個愚蠢的錯誤。對比:
fnn arg1 arg2 arg3 = [arg1, arg3] -- your working code
fnn = \x y z -> [z x] -- your broken code
fnn = \x y z -> [z, x] -- type-correct code
fnn = \x y z -> [x, z] -- more correct translation of your working code
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/530209.html
