functions as applicative functors (Haskell / LYAH)

后端 未结 4 1129
既然无缘
既然无缘 2020-12-02 09:58

Chapter 11 of Learn You a Haskell introduces the following definition:

instance Applicative ((->) r) where
    pure x = (\\_ -> x)
    f <*         


        
相关标签:
4条回答
  • 2020-12-02 10:22

    Going through your original question, I think there's one subtle but very key point that you might have missed. Using the original example from LYAH:

    (+) <$> (+3) <*> (*100) $ 5
    

    This is the same as:

    pure (+) <*> (+3) <*> (*100) $ 5
    

    The key here is the pure before (+), which has the effect of boxing (+) as an Applicative. If you look at how pure is defined, you can see that to unbox it, you need to provide an additional argument, which can be anything. Applying <*> to (+) <$> (+3), we get

    \x -> (pure (+)) x ((+3) x)
    

    Notice in (pure (+)) x, we are applying x to pure to unbox (+). So we now have

    \x -> (+) ((+3) x)
    

    Adding (*100) to get (+) <$> (+3) <*> (*100) and apply <*> again, we get

    \y -> (\x -> (+) ((+3) x)) y ((*100) y) {Since f <*> g = f x (g x)}
    
    5  -> (\x -> (+) ((+3) x)) 5 ((*100) 5)
    
    (\x -> (+) ((+3) x)) 5 (500)
    
    5 -> (+) ((+3) 5) (500)
    
    (+) 8 500
    
    508
    

    So in conclusion, the x after f is NOT the first argument to our binary operator, it is used to UNBOX the operator inside pure.

    0 讨论(0)
  • 2020-12-02 10:23

    “In the instance, substituting ((->)r) for f: r->(a->b)->(r->a)->(r->b)

    Why, that's not right. It's actually (r->(a->b)) -> (r->a) -> (r->b), and that is the same as (r->a->b) -> (r->a) -> r -> b. I.e., we map an infix and a function which returns the infix' right-hand argument, to a function which takes just the infix' LHS and returns its result. For example,

    Prelude Control.Applicative> (:) <*> (\x -> [x]) $ 2
    [2,2]
    
    0 讨论(0)
  • 2020-12-02 10:24

    First of all, remember how fmap is defined for applicatives:

    fmap f x = pure f <*> x
    

    This means that your example is the same as (fmap (+ 5) (* 3)) 4. The fmap function for functions is just composition, so your exact expression is the same as ((+ 5) . (* 3)) 4.

    Now, let's think about why the instance is written the way it is. What <*> does is essentially apply a function in the functor to a value in the functor. Specializing to (->) r, this means it applies a function returned by a function from r to a value returned by a function from r. A function that returns a function is just a function of two arguments. So the real question is this: how would you apply a function of two arguments (r and a, returning b) to a value a returned by a function from r?

    The first thing to note is that you have to return a value of type (->) r which means the result also has to be a function from r. For reference, here is the <*> function:

    f <*> g = \x -> f x (g x)
    

    Since we want to return a function taking a value of type r, x :: r. The function we return has to have a type r -> b. How can we get a value of type b? Well, we have a function f :: r -> a -> b. Since r is going to be the argument of the result function, we get that for free. So now we have a function from a -> b. So, as long as we have some value of type a, we can get a value of type b. But how do we get a value of type a? Well, we have another function g :: r -> a. So we can take our value of type r (the parameter x) and use it to get a value of type a.

    So the final idea is simple: we use the parameter to first get a value of type a by plugging it into g. The parameter has type r, g has type r -> a, so we have an a. Then, we plug both the parameter and the new value into f. We need both because f has a type r -> a -> b. Once we plug both an r and an a in, we have a b1. Since the parameter is in a lambda, the result has a type r -> b, which is what we want.

    0 讨论(0)
  • 2020-12-02 10:35

    How to understand Function as Functor and Function as Applicative?

    First, how to understand function as functor?

    We can regard functor as an empty box such as:

    instance Functor Maybe where 
        fmap :: (a -> b) -> f a -> f b
        fmap f (Just x) = Just (f x) 
        fmap f Nothing = Nothing
    

    there, Maybe type can be seen as an empty box with one slot which take a type to generate a concrete type Maybe a. In the fmap function:

    • The first parameter is a function, which maps from a to b;
    • The second parameter is a value of the type with the slot filled(concrete type), this concrete type is generated by type constructor and has the type f a (f is Maybe, so f a is Maybe a ).

    When we implement function functors, for function functors must have two parameters to make a type a -> b, if we want our function functor has exactly one slot, we should first fill a slot, so the type constructor of function functor is ((->) r):

    instance Functor ((->) r) where 
        fmap f g = (\x -> f (g x))
    

    As the same as the fmap function in Maybe Functor, we should regard the second parameter g as a value of a concrete type which is generate by f (f equals (->) r), so f a is (->) r a which can be seen as r -> a. Finally, it is not difficult to understand that the g x in the fmap function cannot be seen as r -> x, it is just a function application which can be seen as (r -> a) x, also (x -> a).

    Finally, it is not hard to understand that the <*> function in Applicative function (->) r can be implemented as following:

    <*> :: f (a -> b) -> f a -> f b
    <*> :: (r -> a -> b) -> (r -> a) -> (r -> b)
    <&> :: (a -> b) -> (r -> a) -> (r -> b)
    f <*> g = \r -> f r (g r)
    

    for g r will map r to a, f r a will map r, a to b, so the whole lambda function can be seen as r -> b, also f b. For an instance:

    ((+) <*> (+3)) 5
    

    the result is 5 + (5 + 3) = 13.

    How to understand in functions as applicatives, (+) <$> (+3) <*> (*100) $ 5 = 508?

    We know (+) has type: Num a, a -> a -> a;

    We also know (+3) and (*100) has type: Num r, a, r -> a;

    (+) <$> (+3) equals pure (+) <*> (+3), where :t pure (+) equals Num _, a, _ -> a -> a -> a

    In another words, the pure (+) simply takes a _ parameter whatever and return the + operator, the parameter _ has no effect on the final return value. pure (+) also maps the return value of function (+3) to a function. Now for

    f <*> g = \r -> f r (g r)
    

    we can apply the operators and get:

    pure (+) <*> (+3) = 
        \r -> f r (gr) =
        \r -> + (gr) =
        \r -> + (r + 3) =
        \r x -> x + (r + 3)
    

    it has the type r -> x -> a. We then calculate pure (+) <*> (+3) <*> (*100) using the definition of <*>, and get:

    pure (+) <*> (+3) <*> (*100) = 
        \r -> f r (gr) =
        \r -> (r + 3) + (gr)
        \r -> (r + 3) + (r * 100)
    

    then we apply this function with parameter 5, we get:

    (5 + 3) + (5 * 100) = 508 
    

    we can simply think this applicative style as first to calculate the value after <$> and sum them up with the operator before <$>. In last example, this operator is a binary operator equals (+), we can replace it with a triple operator (\x y z -> [x,y,z]), so the following equation holds:

    (\x y z -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5 = [8.0,10.0,2.5]
    
    0 讨论(0)
提交回复
热议问题