应用函子的目的是什么?

时间:2015-02-04 01:50:22

标签: haskell functional-programming

当具有以下签名的函数有用时,任何人都可以分享良好的现实生活情况吗?

f (a -> b) -> f a -> f b

我无法真正看到我需要的地方,例如来自learn-you-a-haskell [(+),(*)] <*> [1,2] <*> [3,4]

的教科书示例

4 个答案:

答案 0 :(得分:6)

应用程序可以用于很多你可能也使用monad的东西,但是并不真正需要那个类的强大功能。 (更准确地说,只要算子的“形状”不依赖于中的值,那么Applicative就足够了。)例如,像

这样的动作
foobar :: IO [String]
foobar = do
   fooTxt <- readFile "foo.txt"
   barTxt <- readFile "bar.txt"
   return $ zip (lines fooTxt) (lines barTxt)

也可以写成

foobar = zip <$> (lines <$> readFile "foo.txt")
             <*> (lines <$> readFile "bar.txt")

在这种情况下,这只会使它变得更短,但在其他情况下,它也可能会提高性能(因为Applicative不太通用,可以进行更多优化)或者允许您使代码更通用您使用Monad界面。

答案 1 :(得分:5)

我最常见的用途是为函数使用多个Monadic(或在本例中为Applicative)值。我使用它的一个非常常见的方法是构造函数。

考虑:

randomAge :: Rand StdGen Int
randomHeight :: Rand StdGen Double
randomWeight :: Rand StdGen Double

data Person = Person { age :: Int, height :: Double, weight :: Double }

randomPerson :: Rand StdGen Person
randomPerson = Person <$> randomAge <*> randomHeight <*> randomWeight

-- If we only had Monads...
randomPerson' :: Rand StdGen Person
randomPerson' = liftM3 Person randomAge randomHeight randomWeight
-- or worse...
randomPerson'' = randomAge >>= \ra -> randomHeight >>= \rh -> randomWeight >>= \rw -> return $ Person ra rh rw

FPComplete's tutorial on the JSON parser Aeson中的此示例:

instance FromJSON Person where
 parseJSON (Object v) =
    Person <$> v .: "firstName"
           <*> v .: "lastName"
           <*> v .: "age"
           <*> v .: "likesPizza"
 parseJSON _ = mzero

Real World Haskell chapter on Parser Combinators中的此示例:

ghci> let parser = (++) <$> string "HT" <*> (string "TP" <|> string "ML")

大多数情况下,当您拥有f :: a -> b -> cm am b并且您需要m c时,申请人会很有用。

编辑:那说还有其他用途。例如,Applicative可以用作修改可遍历数据结构的非常好的方法。例如。通过具有函数树将树应用于值,从而将值转换为树。或者在没有列表理解语法的情况下进行非确定性列表操作。

它可能有一些意想不到的应用程序。例如,使用-> Monad:

> (<*>) (+) (+1) 2
5

最后一个有点深奥,并且不太可能在实际环境中使用,但它表明你可以在很多方面使用Applicatives。

答案 2 :(得分:3)

它是pure等其他非常有用的功能的基本构建块(以及liftA2)(您可以将其视为“fmap超过两个容器”并且不会太远离开商标):

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2 f fa fb = pure f <*> fa <*> fb

因此,假设您要添加两个列表xs, ys :: [Integer]的值的所有组合,您可以这样做:

liftA2 (+) xs ys

虽然我们更常见的是写这个:

(+) <$> xs <*> ys

你感到困惑,为什么有人会写[(+),(*)] <*> [1,2] <*> [3,4] - 我认为这是一个非常人为的例子。 99%的时间使用Applicative,你基本上有一个函数,它接受两个或多个参数,并且希望将它应用于实现{{1}的仿函数内的值}}

查看它的一种方法是我们可以选择定义Applicative和相关的函数,如下所示:

Applicative

但选择-- Not the real definition class Functor f => Applicative f where pure :: a -> f a -- Like `fmap` but for two-argument functions: liftA2 :: (a -> b -> c) -> f a -> f b -> f c -- `<*>` and `liftA2` are interdefinable, as shown further up and just here: (<*>) :: Applicative f => f (a -> b) -> f a -> f b ff <*> fa = liftA2 ($) ff fa 作为类方法的原因是它可以很容易地编写<*>liftA2等等:

liftA3

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c liftA2 f fa fb = pure f <*> fa <*> fb liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d liftA3 f fa fb fc = pure f <*> fa <*> fb <*> fc liftA4 :: Applicative f => (a -> b -> c -> d -> e) -> f a -> f b -> f c -> f d -> f e liftA4 f fa fb fc fd = pure f <*> fa <*> fb <*> fc <*> fd 类几乎是支持这种“具有多个参数函数的映射”模式所需的最小功能,这在很多情况下都是有用的。

答案 3 :(得分:1)

如左下所述,每当&#34;形状&#34;每个&#34;行动&#34;不依赖于价值&#34;生产&#34;通过前一个,Applicative就足够了。

这可能是一种非常直观的形状。例如,>>=的一个上下文是t >>= f获取存储在树t的叶子中的值,将f应用于每个值以生成新的子树,以及移植每个子树到树上代替相应的叶子。但是,此操作对平衡树不起作用,因为没有人知道树f每次会产生多大/多深。然而,<*>可能仍然有意义,因为嫁接树将具有相同的形状,保持平衡。