Examples of Haskell Applicative Transformers

后端 未结 4 549
别跟我提以往
别跟我提以往 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:10

    Consider the following type signatures:

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

    Combined, the resulting type is:

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

    This is indeed a combination of two Applicatives. In fact, it's a combination of exactly two Applicatives. In other words, although you can combine Applicatives in a generic way, this is not in any way way done automatically. Everything must be explicitly lifted the correct number of times.

    Your ex function is equivalent to liftA2 (:), which has type (Applicative f) => f a -> f [a] -> f [a]. Going through your examples, making some guesses about what you wanted to do:

    test1 = ex "abc" ["pqr", "xyz"]
    

    Here f is [], and we're applying it to arguments of type [Char] and [[Char]].

    test2 = ex "abc" [Just "pqr", Just "xyz"]
    

    The second argument is of type [Maybe [Char]], so we need to lift twice. The first argument also needs to be lifted, since it has type [Char] and should be [Maybe Char].

    test3 = ex "abc" (Just "pqr")
    

    This time the second argument is of type Maybe [Char], so f is Maybe and we only need one lift. The first argument should therefore be of type Maybe Char.

    test4 = ex (Just 'a') ["pqr", "xyz"]
    

    This time the first argument is Maybe Char but the second is [[Char]], so you have two completely different Applicatives; both will need to be lifted, to give you either [Maybe Char] or Maybe [Char].

    test5 = ex (return ("abc"):: IO ()) [Just "pqr", Just "xyz"]
    

    The type signature here makes no sense; you probably wanted IO [Char]. The second argument has type [Maybe [Char]]. Like the previous example they don't match up, but this time you have three Applicatives. If you want something like IO [Maybe a], you'll need to lift (:) all three times, e.g. liftA2 (liftA2 ex).

    This way of combining Applicatives is called "functor composition", and the page you linked to mentions libraries that define an explicit composition type constructor. For example, using the transformers library, you could have a type like Compose IO (Compose [] Maybe) to describe your fifth example. This composed type is defined as an Applicative instance in the aforementioned generic way, and applies the correct number of lifting operations. The downside is that you'll need to wrap and unwrap the newtype layers this requires.


    As an addendum, this statement:

    So where are applicative transformers? The answer is, that we do not need special transformers for applicative functors since they can be combined in a generic way.

    ...is a bit bogus. It's true that the composition of two Applicatives is also Applicative, but this is not the only way to combine Applicatives!

    Consider StateT s m a, which is equivalent to s -> m (s, a), though it's defined slightly differently. This could also be written as the composition of three functors: ((->) s), m, and ((,) s), and the resulting Functor instance would be correct, but the Applicative instance would be completely wrong. If you start with just State s a = s -> (a, s) instead, there's no way to define StateT s m by composing State s and m.

    Now, observe that the non-composition combination StateT s (Either e) is essentially a simplified version of the typical parser combinator monad used in libraries like Parsec, and such parsers are one of the well-known places where using Applicative style is popular. As such, it seems more than a bit misleading to suggest that monad transformer-style combinations are somehow unnecessary or superfluous where Applicative is concerned!

提交回复
热议问题