函数的函子、应用函子和单子实例的使用案例

20

Haskell标准库中为函数(特别是部分应用类型 (->) a)定义了FunctorApplicativeMonad实例,围绕函数组合构建。

理解这些实例是一种不错的头脑体操练习,但我在这里想问的是这些实例的实际用途。我很乐意听听人们在某些实际代码中使用这些实例的现实场景。


8
阅读器单子(Reader monad)基本上只是(->)的一个新类型包装器。 - melpomene
我经常使用它们。也许你自己在不知不觉中就用到了:. 就是 fmap - Bergi
@Bergi:当然,我想问题是为什么使用它们而不是只使用(.)。 - Eli Bendersky
2
@Bergi 组合与 Functor 实例无关;更准确地说,fmap 就是 . - chepner
3个回答

7
一个常见的模式涉及到函数的Functor和Applicative实例,例如(+) <$> (*2) <*> (subtract 1)。当您需要用单个值提供一系列函数时,这非常有用。在这种情况下,上面的表达式等价于\x -> (x * 2) + (x - 1)。虽然这非常接近于LiftA2,但是您可以无限扩展此模式。如果您有一个f函数,它需要5个参数,例如a -> a -> a -> a -> a -> b,则可以使用f <$> (+2) <*> (*2) <*> (+1) <*> (subtract 3) <*> (/2)并将其用单个值进行输入。就像下面的案例中一样;
Prelude> (,,,,) <$> (+2) <*> (*2) <*> (+1) <*> (subtract 3) <*> (/2) $ 10
(12.0,20.0,11.0,7.0,5.0)

编辑:感谢@Will Ness在另一个话题下对我发表的评论,这里介绍一种函数应用的美妙用法;

Prelude> let isAscending = and . (zipWith (<=) <*> drop 1)
Prelude> isAscending [1,2,3,4]
True
Prelude> isAscending [1,2,5,4]
False

感谢您的输入。所以您发布的5元组示例可以重写为(\x -> (x+2, x*2, x+1, x-3, x/2)) 10--这里应用程序的优点是不需要重复使用x - Eli Bendersky
@Eli Bendersky,主要区别在于,在应用形式中,如果您知道主函数需要多少个参数,则整个过程是可分解的(您可以动态地组合参数),而在λ形式中,您必须手头有一个可靠的λ函数。 - Redu
你是说我们已经有一个函数,像 (,,,,),不需要手写 lambda 吗? - Eli Bendersky
@Eli Bendersky 是的...整个事情可能是另一个函数定义,它以应用函数 (+2)(*2)(+1)(subtract 3)( /2) 作为参数,并且您可以将 subtract 3 替换为 subtract 1 并应用它。 - Redu
我猜我有点明白你想说什么,但如果没有具体的用例,很难让我想象。 :) - Eli Bendersky
@Eli Bendersky 我的意思是 lambda 函数 (\x -> (x+2, x*2, x+1, x-3, x/2)) 在使用时是硬编码的,而在应用风格中则不是。可以使用 applicative 风格创建一个新函数来动态更改“映射”函数。具体的用例可能不容易 spontaneously 想出,但这是一个很好的知识点需要记在心里。 - Redu

4
有时候,您需要将形式为 a -> m b (其中 m 是一个 Applicative)的函数视为 Applicative。这通常发生在编写验证器或解析器时。
一种方法是使用 Data.Functor.Compose,它依赖于 (->) amApplicative 实例来给出组合的 Applicative 实例:
import Control.Applicative
import Data.Functor.Compose

type Star m a b = Compose ((->) a) m b

readPrompt :: Star IO String Int
readPrompt = Compose $ \prompt -> do
    putStrLn $ prompt ++ ":"
    readLn

main :: IO ()
main = do
    r <- getCompose (liftA2 (,) readPrompt readPrompt) "write number"
    print r

还有其他方法,比如创建自己的新类型,或者使用来自base或其他库的现成的新类型


0

这里是我用于解决Diamond Kata的bind函数的一个应用实例。使用一个简单的函数来镜像其输入并丢弃最后一个元素。

mirror :: [a] -> [a]
mirror xs = xs ++ (reverse . init) xs

让我们稍微重写一下它

mirror xs = (++) xs ((reverse . init) xs)
mirror xs = flip (++) ((reverse . init) xs) xs
mirror xs = (reverse . init >>= flip (++)) xs
mirror = reverse . init >>= flip (++)

这是我对这个 Kata 的完整实现:https://github.com/enolive/exercism/blob/master/haskell/diamond/src/Diamond.hs


由于 ma >>= f 等于 flip f <*> ma,因此您可以将自己限制在函数的 Applicative 实例上,即 mirror = (++) <*> reverse . init。不使用 (>>=) 和函数的 Monad 实例似乎是合理的。 - Micha Wiedenmann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接