Examples of Haskell Applicative Transformers

后端 未结 4 553
别跟我提以往
别跟我提以往 2021-02-08 06:41

The wiki on www.haskell.org tells us the following about Applicative Transformers:

So where are applicative transformers? The answer is, that we do not ne

4条回答
  •  攒了一身酷
    2021-02-08 07:00

    As usual, it's useful here to focus on the types to figure out what composition of applicative functors should mean.

    If we write a for the type of a particular pure value x, that therefore has no side effects, then we can lift this pure value to a computation of the applicative functor f using the pure combinator. But by the same token, we can use the pure function from g's Applicative instance to lift pure x into the g functor.

    pure (pure x) :: g (f a)
    

    Now g (f a) is the type of computations that combine the effects of g and the effects of f. Looking at your tests, we notice that

    test1 :: [String]
    

    You have used only one effect in test1, that is the non-determinism that the list instance of Applicative gives you. Indeed, breaking it down:

    "abc" :: String
    ((:) <$>) :: [Char] -> [String -> String]
    ((:) <$> "abc") :: [String -> String]
    ((:) <$> "abc" <*> ["pqr", "xyz"]) :: [String]
    

    Now, if we want to compose the failure effect and the non-determinism effect, we would expect to construct a computation of type Maybe [a], or perhaps [Maybe a]. It turns out that the two are equivalent, because applicative functors always commute.

    Here's a computation of type [Maybe Char]. It will non-deterministally return a Char, but if it does, it might fail:

    x1 = [Just 'a', Just 'b', Just 'c']
    

    Likewise, here's a computation of type [Maybe String]:

    x2 = [Just "pqr", Just "xyz"]
    

    Now we want to lift (:) to this combined applicative functor. To do so, we have to lift it it twice:

    pure (pure (:)) :: [Maybe (Char -> String -> String)]
    

    Likewise, to apply it, we need to push this computation through both functors. We can therefore introduce a new combinator (<<*>>) that does that:

    (<<*>>) :: (Applicative f, Applicative f1) =>
               f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)
    (<<*>>) = liftA2 (<*>)
    

    Which now allows us to write:

    pure (pure (:)) <<*>> x1 <<*>> x2
    

    which you can check has the expected type.

    But since applicative functors are closed under composition, [Maybe a] is itself an applicative functor and so you may wish to be able to reuse pure and (<*>). The Data.Functor.Compose module of the transformers package shows you how to.

提交回复
热议问题