📜 ⬆️ ⬇️

Learn Haskel for good! Applicative Functors

More recently, the publisher No Starch Press has prepared and published a print edition of the wonderful textbook Learn You a Haskell for Great Good! (online version) written by Miran LipovaÄŤa.

I want to present you the most current translation of Chapter 11 Applicative functors , the original for which was the publication from No Starch Press , adapted for printing.

Applicative Functors


The combination of purity, higher-order functions, parameterized algebraic data types, and type classes in Haskell makes the implementation of polymorphism simpler than in other languages. We do not need to think about types belonging to a large hierarchy. Instead, we look at how types can act, and then we connect them using the appropriate type classes. Int can behave like a multitude of entities — a compared entity, an ordered entity, an enumerated entity, and so on.

Type classes are open, which means that we can define our own data type, think about how it can act, and associate it with the type classes that define its behavior. We can also introduce a new type class, and then make existing types of it instances. For this reason, and thanks to the excellent Haskell type system, which allows us to know a lot about a function only by its type declaration, we can define classes of types that describe very general and abstract behavior.
')
We talked about classes of types that define operations to check whether two elements are equal and to compare two elements by placing them in some order. These are very abstract and elegant behaviors, although we do not think of them as something very special, since we have dealt with them for most of our lives. In Chapter 7, functors were introduced, which are types whose values ​​can be displayed. This is an example of a useful and still rather abstract property that type classes can describe. In this chapter, we will take a closer look at functors, as well as with slightly stronger and more useful versions of functors, which are called applicative functors.

Functors are returned.



As you learned in Chapter 7, functors are entities that can be displayed, such as lists, Maybe values, and trees. In Haskell, they are described by the Functor type class, which contains only one type class method: fmap . fmap is of type fmap :: (a -> b) -> fa -> fb , which says, “Give me a function that accepts a and returns b and a box containing a (or several a) inside, and I will return the box with b (or several b ) inside. ”It applies the function to the item inside the box.

We can also perceive the values ​​of functors as values ​​with an additional context. For example, Maybe values ​​have an additional context that the calculations might have failed. In relation to lists, the context is that the value may be multiple or absent. fmap applies a function to a value, keeping its context.

If we want to make a type constructor an instance of Functor , it must be * -> * , which means that it takes exactly one particular type as a type parameter. For example, Maybe can be made an instance, since it takes one type parameter to produce a particular type, such as Maybe Int or Maybe String . If the type constructor takes two parameters, like Either, we must partially apply the type constructor until it takes only one parameter. Therefore, we cannot write Functor Either where , but we can write Functor (Either a) where . Then, if we imagined that fmap intended only for working with Either a , it would have the following type description:

 fmap :: (b -> c) -> Either ab -> Either ac 


As you can see, the Either a part is fixed because Either a takes only one type parameter.

I / O actions as functors



You have now learned how many types (to be exact, type constructors) are instances of Functor : [] and Maybe , Either a , and also the type Tree that we created in Chapter 7. You saw how you can display them using functions for the sake of good. Now let's take a look at the IO instance.

If, say, an IO String type has any value, it means that it is an I / O action that will go out into the real world and get some string for us, which it then returns as a result. We can use <- in the do syntax to bind this result to a name. In Chapter 8, we talked about how I / O actions look like boxes with small legs that go outside and get some value for us from the outside world. We can see what they brought, but after viewing we need to wrap the value back into IO . Considering this analogy of boxed legs, you can understand how IO acts as a functor.

Let's see how IO is an instance of Functor . When we use fmap to display an I / O action with a function, we want to get back an I / O action that does the same thing, but our function applies to its resulting value. Here is the code:
 instance Functor IO where fmap f action = do result <- action return (f result) 

The result of displaying an I / O action with something will be an I / O action, so we immediately use the do syntax to glue the two actions together and create one new one. In the implementation for fmap we create a new I / O action that first performs the initial I / O action, giving the result the name result . Then we execute return (f result) . Recall that return is a function that creates an I / O action that does nothing, but only returns something as its result.

The action that the do block produces will always return the resulting value of its last action. That's why we use return to create an I / O action that doesn’t really do anything, but simply returns f result as the result of a new I / O action. Take a look at this piece of code:

 main = do line <- getLine let line' = reverse line putStrLn $ "You said " ++ line' ++ " backwards!" putStrLn $ "Yes, you really said" ++ line' ++ " backwards! 


The user is prompted for a string, and we give it back to the user, but upside down. This is how you can rewrite it using fmap :

 main = do line <- fmap reverse getLine putStrLn $ "You said " ++ line ++ " backwards!" putStrLn $ "Yes, you really said" ++ line ++ " backwards!" 


alien Just as we can display Just "blah" using fmap reverse , getting Just "halb" , we can display getLine using fmap reverse . getLine is an I / O action that has an IO String type and displaying it with reverse gives us an I / O action that will go out into the real world and get a string, and then apply reverse to its result. In the same way that we can apply a function to what's inside the Maybe box, we can apply the function to what's inside the IO box, but it must go to the real world to get something. Then, when we bind the result to a name using <- , the name will reflect the result to which reverse has already been applied.

The fmap (++"!") getLine I / O operation of fmap (++"!") getLine behaves exactly like getLine , except that the result is always added to the "!" In the end!

If fmap worked only with IO , it would have the type fmap :: (a -> b) -> IO a -> IO b . fmap takes a function and an I / O operation, and returns a new I / O operation, similar to the old one, except that a function is applied to the result contained in it.

If you ever find yourself in a situation where you associate the result of an I / O operation with a name just to apply a function to it, and then give the next result some other name, consider using fmap . If you want to apply several functions to some data inside a functor, you can declare your function at the top level, create a lambda function, or, ideally, use a composition of functions:

 import Data.Char import Data.List main = do line <- fmap (intersperse '-' . reverse . map toUpper) getLine putStrLn line 


This is what happens if we run this code and enter hello there:
 $ runhaskell fmapping_io hello there EREHT- -OLLEH 

intersperse '-' . reverse . map toUpper function intersperse '-' . reverse . map toUpper intersperse '-' . reverse . map toUpper intersperse '-' . reverse . map toUpper takes a string, displays it with toUpper , applies reverse to this result, and then applies intersperse '-' to this result. This is a more beautiful way to write the following code:

(\xs -> intersperse '-' (reverse (map toUpper xs)))

Functions as functors



Another instance of Functor that we have been dealing with all the time is (->) r . Stand still What the hell does (->) r mean? The type of the function r -> a can be rewritten as (->) ra , just as we can write 2 + 3 as (+) 2 3 . When we perceive it as (->) ra , we see (->) slightly different light. It's just a type constructor that takes two type parameters, as Either does.

But remember that the type constructor must take exactly one type parameter so that it can be made an instance of Functor . That is why we cannot make (->) instance of Functor ; however, if you partially apply it before (->) r , this does not pose any problems. If the syntax would allow partial use of type constructors using sections (as we can use + by doing (2+) , which is similar to (+) 2 ), you could write (->) r as (r->) .

How are functions functors? Well, let's take a look at the implementation that is in Control.Monad.Instances .

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


Let's first think about the fmap type:
 fmap :: (a -> b) -> fa -> fb 

Next, let's mentally replace each f , which is the role that our copy of the functor plays, with (->) r . This allows us to understand how fmap should behave in the case of this particular instance. Here is the result:
 fmap :: (a -> b) -> ((->) ra) -> ((->) rb) 

Now we can write the types (->) ra and (->) rb in infix form, i.e.
r -> a and r -> b , as we usually do with functions:
 fmap :: (a -> b) -> (r -> a) -> (r -> b) 

Good. Displaying one function with another function should produce a function, just like Maybe displaying with a function should produce Maybe , and displaying a list with a function should produce a list. What does the previous type tell us? We see that it takes a function from a to b and a function from r to a and returns a function from r to b . Does this remind you of anything? Yes, the composition of functions! We attach the output r -> a to the input a -> b to get the function r -> b , which is exactly the composition of the functions. Here is another way to record this instance:
 instance Functor ((->) r) where fmap = (.) 

This code makes it clear that applying fmap to functions is simply a composition of functions. In the script, import Control.Monad.Instances , since this is the module where this instance is defined, then load the script and try to play with the display of functions:
 ghci> :t fmap (*3) (+100) fmap (*3) (+100) :: (Num a) => a -> a ghci> fmap (*3) (+100) 1 303 ghci> (*3) `fmap` (+100) $ 1 303 ghci> (*3) . (+100) $ 1 303 ghci> fmap (show . (*3)) (*100) 1 "300" 

We can call fmap as an infix function to make a similarity with . was obvious. In the second input line, we display (+100) with (*3) , which gives a function that accepts input, applies it (+100) , and then applies (*3) to this result. Then we apply this function to 1 .

Like all functors, functions can be perceived as values ​​with contexts. When we have a function like (+3) , we can consider the value as the final result of the function, and the context is that we have to apply this function to something to get the result. Applying fmap (*3) to (+100) will create another function that acts the same as (+100) , but before returning the result, (*3) will be applied to this result.

The fact that fmap is a composition of functions when applied to functions is not too useful at the moment, but at least it is very interesting. It also changes our consciousness a little and allows us to see how entities that act more like calculations than boxes ( IO and (->) r ) can be functors. The display of the calculation using the function returns the same type of calculation, but the result of this calculation is changed by the function.

lifter
Before moving on to the laws that fmap should follow, let's think again about the type of fmap :

 fmap :: (a -> b) -> fa -> fb 


Introduction to curried functions in Chapter 5 began with the statement that all functions in Haskell actually take one parameter. The function a -> b -> c actually takes only one parameter of type a , and then returns the function b -> c , which takes one parameter and returns c . That is why calling a function with an insufficient number of parameters (its partial application) returns us back to a function that takes several parameters that we missed (if we again perceive functions as if they accept several parameters). Therefore, a -> b -> c can be written as a -> (b -> c) to make currying more obvious.

In the same vein, if we write fmap :: (a -> b) -> (fa -> fb) , we can take fmap not as a function that takes one function and a value of a functor and returns the value of a functor, but as a function which takes a function and returns a new function that is the same as the previous one, except that it takes the value of the functor as a parameter and returns the value of the functor as a result. It takes the function a -> b and returns the function fa -> fb . This is called "lifting function." Let's play with this idea using the command :t in GHCi:

 ghci> :t fmap (*2) fmap (*2) :: (Num a, Functor f) => fa -> fa ghci> :t fmap (replicate 3) fmap (replicate 3) :: (Functor f) => fa -> f [a] 


The fmap (*2) expression is a function that takes a functor f over numbers and returns a functor over numbers. This functor can be a list, the value Maybe , Either String , or something else. The fmap (replicate 3) expression fmap (replicate 3) will get a functor over any type and return the functor over the list of elements of this type. This becomes even more obvious if we partially apply, say, fmap (++"!") , And then assign it to a name in GHCi.

You can perceive fmap two ways:



Both points of view are correct.

The type fmap (replicate 3) :: (Functor f) => fa -> f [a] means that the function will work with any functor. What exactly it will do depends on the functor. If we apply fmap (replicate 3) to the list, the fmap implementation will be chosen for the list, i.e. just map . If we apply it to Maybe a , it will apply replicate 3 to the value inside Just . If this value is Nothing , then it will remain Nothing . Here are some examples:

 ghci> fmap (replicate 3) [1,2,3,4] [[1,1,1],[2,2,2],[3,3,3],[4,4,4]] ghci> fmap (replicate 3) (Just 4) Just [4,4,4] ghci> fmap (replicate 3) (Right "blah") Right ["blah","blah","blah"] ghci> fmap (replicate 3) Nothing Nothing ghci> fmap (replicate 3) (Left "foo") Left "foo" 


The laws of functors



It is assumed that all functors exhibit certain types of properties and behaviors. They must behave reliably as entities that can be displayed. Applying fmap to a functor should only display a functor using a function — nothing more. This behavior is described in the laws of functors. All copies of Functor must follow these two laws. Haskell does not force these laws to be carried out automatically, so you should check them yourself when creating a functor. All Functor instances in the standard library obey these laws.

Act 1



The first law of functors states that if we apply the function id to the value of a functor, then the value of the functor we get should be the same as the original value of the functor. In a bit more formal, this means that fmap id = id . Essentially, it says that if we apply fmap id to the value of a functor, it should be the same as simply applying id to the value. Recall that id is an identity function that simply returns its parameter unchanged. It can also be written as \x -> x . If we take the value of a functor as something that can be displayed, then the law fmap id = id looks rather trivial and obvious.

Let's see if this law holds for some values ​​of functors.

 ghci> fmap id (Just 3) Just 3 ghci> id (Just 3) Just 3 ghci> fmap id [1..5] [1,2,3,4,5] ghci> id [1..5] [1,2,3,4,5] ghci> fmap id [] [] ghci> fmap id Nothing Nothing 


If you look at the implementation of fmap , for example, for Maybe , we can see why the first law of functors is true:

 instance Functor Maybe where fmap f (Just x) = Just (fx) fmap f Nothing = Nothing 


We imagine that id plays the role of the parameter f in this implementation. We see that if we apply fmap id to Just x , the result will be Just (id x) , and since id simply returns its parameter, we can conclude that Just (id x) is equal to Just x . Therefore, we now know that if we apply id to the Maybe value created using the Just value constructor, we get the same value back.

To see that applying id to Nothing returns the same value is trivial. Therefore, from these two equalities in the implementation of fmap we see that the law fmap id = id is observed.

Act 2



justice The second law says that the composition of two functions and the subsequent application of the resulting function to the functor should give the same result as the application of the first function to the functor, and then the application of another function. In the formal notation, this means that fmap (f . g) = fmap f . fmap g fmap (f . g) = fmap f . fmap g . Or if we write it differently, then for any value of the functor x following should be fmap (f . g) x = fmap f (fmap gx) : fmap (f . g) x = fmap f (fmap gx) .

If we can reveal that a certain type obeys the two laws of functors, we can hope that it possesses the same fundamental behaviors as other functors when it comes to mapping. We can know that when we apply fmap to it, nothing happens behind the scenes except the display, and that it will act as an entity that can be displayed — that is, a functor.

We can figure out how the second law is fulfilled with respect to some type by looking at the implementation of fmap for that type, and then using the method we used to check if Maybe obeys the first law. So, to test how the second law of functors holds for Maybe , if we apply fmap (f . g) to Nothing , we get Nothing , because applying any function to Nothing gives Nothing . If we do fmap f (fmap g Nothing) , we get Nothing for the same reasons.

It's pretty easy to see how the second law is executed for Maybe when the value is Nothing . , Just ? , fmap (f . g) (Just x) , , Just ((f . g) x) , Just (f (gx)) . fmap f (fmap g (Just x)) , , fmap g (Just x) Just (gx) . , fmap f (fmap g (Just x)) fmap f (Just (gx)) , , Just (f (gx)) .

, . , , . , , . , .



, Functor, , . , :
 data CMaybe a = CNothing | CJust Int a deriving (Show) 

C . , Maybe a, Just . CJust Int , . a , , , CMaybe a . :

 ghci> CNothing CNothing ghci> CJust 0 "haha" CJust 0 "haha" ghci> :t CNothing CNothing :: CMaybe a ghci> :t CJust 0 "haha" CJust 0 "haha" :: CMaybe [Char] ghci> CJust 100 [1,2,3] CJust 100 [1,2,3] 


CNothing , . CJust , , — . Functor , , fmap , , 1 .

 instance Functor CMaybe where fmap f CNothing = CNothing fmap f (CJust counter x) = CJust (counter+1) (fx) 


Maybe , fmap , ( CJust ), , 1 . . :

 ghci> fmap (++"ha") (CJust 0 "ho") CJust 1 "hoha" ghci> fmap (++"he") (fmap (++"ha") (CJust 0 "ho")) CJust 2 "hohahe" ghci> fmap (++"blah") CNothing CNothing 


? , , - , .

 ghci> fmap id (CJust 0 "haha") CJust 1 "haha" ghci> id (CJust 0 "haha") CJust 0 "haha" 


, id, , id . , CMaybe . Functor , , , .

CMaybe , , . , , , , . CMaybe , , . ! , CMaybe , , Int , fmap .

. , , , . , , fmap — . , , , , , , .

, Functor , , , . , , . , , , .


present
, .

, . , , ? :

Just 3 fmap (*) (Just 3) , ? Maybe Functor , Just , Just . fmap (*) (Just 3) Just ((*) 3) , Just (3 *) , . Interesting! , Just !

:

 ghci> :t fmap (++) (Just "hey") fmap (++) (Just "hey") :: Maybe ([Char] -> [Char]) ghci> :t fmap compare (Just 'a') fmap compare (Just 'a') :: Maybe (Char -> Ordering) ghci> :t fmap compare "A LIST OF CHARS" fmap compare "A LIST OF CHARS" :: [Char -> Ordering] ghci> :t fmap (\xyz -> x + y / z) [3,4,5,6] fmap (\xyz -> x + y / z) [3,4,5,6] :: (Fractional a) => [a -> a -> a] 


compare , (Ord a) => a -> a -> Ordering , Char -> Ordering , compare . (Ord a) => a -> Ordering , a Char , a , Char .

, «» , , . ? , , , , , , , .

 ghci> let a = fmap (*) [1,2,3,4] ghci> :ta a :: [Integer -> Integer] ghci> fmap (\f -> f 9) a [9,18,27,36] 


, Just (3 *) Just 5 , Just (3 *) Just 5 ? , . , , \f -> f 9 , . , fmap , , , . Just , Just 5 , , .



Applicative, Control.Applicative . : pure <*> . - , , - . :

 class (Functor f) => Applicative f where pure :: a -> fa (<*>) :: f (a -> b) -> fa -> fb 


! Applicative , . , Applicative , , , Functor . , Applicative , Functor , fmap .

, , pure . pure :: a -> fa . f . , , — , , — .

pure . « », , , . a -> fa . , . pure — , ( ) — , - .

<*> . :

 f (a -> b) -> fa -> fb 


-? fmap :: (a -> b) -> fa -> fb . <*> fmap . fmap , , <*> , , , , .

Maybe



Applicative Maybe :

 instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something 


, , f , , , instance Applicative Maybe where instance Applicative (Maybe a) where .

, pure . , - . pure = Just , Just . pure x = Just x .

, <*> . Nothing , . , Nothing , Nothing .

Applicative Functor , , , <*> . Nothing , Just , , . , Nothing , Nothing , fmap , Nothing . , Maybe <*> , Just , . - Nothing , Nothing .

:

 ghci> Just (+3) <*> Just 9 Just 12 ghci> pure (+3) <*> Just 10 Just 13 ghci> pure (+3) <*> Just 9 Just 12 ghci> Just (++"hahah") <*> Nothing Nothing ghci> Nothing <*> Just "woot" Nothing 


, pure (+3) Just (+3) — . pure , Maybe ( <*> ); Just .

, , , , . , Nothing , - , Nothing .

, - , . , , , .



Applicative <*> , , . , , :

 ghci> pure (+) <*> Just 3 <*> Just 5 Just 8 ghci> pure (+) <*> Just 3 <*> Nothing Nothing ghci> pure (+) <*> Nothing <*> Just 5 Nothing 


whale + , <*> , , .

, , . <*> , , :
 pure (+) <*> Just 3 <*> Just 5 

, :
 (pure (+) <*> Just 3) <*> Just 5 

+ — Maybe , . , pure (+) , Just (+) . , Just (+) <*> Just 3 . Just (3+) . - . 3 + , 3 . , Just (3+) <*> Just 5 , Just 8 .

?! pure f <*> x <*> y <*> ... , , , . , , <*> .

, , pure f <*> x fmap fx . . , , . pure . , , , . pure f <*> x <*> y <*> ... , fmap fx <*> y <*> ... . Control.Applicative , <$> , fmap . :
 (<$>) :: (Functor f) => (a -> b) -> fa -> fb f <$> x = fmap fx 


: , . f , , , f , Functor . f , x . , f , .

<$> , f , f <$> x <*> y <*> z . , fxyz .

, . , Just "johntra" Just "volta" , Maybe . :
 ghci> (++) <$> Just "johntra" <*> Just "volta" Just "johntravolta" 

, , :
 ghci> (++) "johntra" "volta" "johntravolta" 

, <$> <*> , . ?

(++) <$> Just "johntra" <*> Just "volta" : (++) , (++) :: [a] -> [a] -> [a] Just "johntra" . , Just ("johntra"++) , Maybe ([Char] -> [Char]) . , (++) a Char . Just ("johntra"++) <*> Just "volta" , Just Just "volta" , Just "johntravolta" . Nothing , Nothing .

Lists


( , [] ) . ! [] Applicative :
 instance Applicative [] where pure x = [x] fs <*> xs = [fx | f <- fs, x <- xs] 

, pure . , , . , , , pure . pure . , Maybe Nothing , , pure Maybe Just .

pure :
 ghci> pure "Hey" :: [String] ["Hey"] ghci> pure "Hey" :: Maybe String Just "Hey" 

<*> ? <*> , (<*>) :: [a -> b] -> [a] -> [b] . . <*> - , . , , , . . . .

<*> :
 ghci> [(*0),(+100),(^2)] <*> [1,2,3] [0,0,0,101,102,103,1,4,9] 

, , . . , , .

:
 ghci> [(+),(*)] <*> [1,2] <*> [3,4] [4,5,5,6,3,4,6,8] 

<*> , [(+),(*)] <*> [1,2] , , [(1+),(2+),(1*),(2*)] , . [(1+),(2+),(1*),(2*)] <*> [3,4] , .

!
 ghci> (++) <$> ["ha","heh","hmm"] <*> ["?","!","."] ["ha?","ha!","ha.","heh?","heh!","heh.","hmm?","hmm!","hmm."] 

, , , , .

. 100 "what" , , [1,2,3] , , , . - (+) <$> [1,2,3] <*> [4,5,6] , + , , .

. 1 [2,5,10] [8,10,11] , :
 ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]] [16,20,22,40,50,55,80,100,110] 

. :

 ghci> (*) <$> [2,5,10] <*> [8,10,11] [16,20,22,40,50,55,80,100,110] 


, , * . , 50 , :

 ghci> filter (>50) $ (*) <$> [2,5,10] <*> [8,10,11] [55,80,100,110] 


, pure f <*> xs fmap f xs . pure f — [f] , [f] <*> xs , , .

IO



Applicative , , IO . :
 instance Applicative IO where pure = return a <*> b = do f <- a x <- b return (fx) 

knight
pure , , , pure — return . return -, . - , - .

<*> IO , (<*>) :: IO (a -> b) -> IO a -> IO b . IO - a , , - f . - b x . , f x . do . (, do , - .)

Maybe [] <*> , . IO , , - . -, , -, . :

 myAction :: IO String myAction = do a <- getLine b <- getLine return $ a ++ b 


-, . - getLine return , - a ++ b . :

 myAction :: IO String myAction = (++) <$> getLine <*> getLine 


, , -, -. , getLine — -, getLine :: IO String . <*> , , .

, getLine , . (++) <$> getLine <*> getLine , ' , , .

(++) <$> getLine <*> getLine IO String . , -, , , -. :

 main = do a <- (++) <$> getLine <*> getLine putStrLn $ "The two lines concatenated turn out to be: " ++ a 




Applicative (->) r . , , , .

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


pure , , , . - . pure , . pure (->) r pure :: a -> (r -> a) .

 ghci> (pure 3) "blah" 3 


- , :

 ghci> pure 3 "blah" 3 


<*> , , :

 ghci> :t (+) <$> (+3) <*> (*100) (+) <$> (+3) <*> (*100) :: (Num a) => a -> a ghci> (+) <$> (+3) <*> (*100) $ 5 508 


<*> , , . What is going on here? (+) <$> (+3) <*> (*100) , , + (+3) (*100) . (+) <$> (+3) <*> (*100) $ 5 , (+3) (*100) 5 , 8 500 . + 8 500 , 508 .

:

 ghci> (\xyz -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5 [8.0,10.0,2.5] 


jazzb , \xyz -> [x, y, z] , (+3) , (*2) (/2) . 5 , \xyz -> [x, y, z] .

: , , (->) r Applicative , , . , , .



, . : <*> , .

, [(+3),(*2)] <*> [1,2] , (+3) 1 2 , (*2) 1 2 , : [4,5,2,4] . , [(+3),(*2)] <*> [1,2] , , , . . : [4,4] . [1 + 3, 2 * 2] .

Applicative , , ZipList , Control.Applicative .

, ZipList a , (ZipList) (). :

 instance Applicative ZipList where pure x = ZipList (repeat x) ZipList fs <*> ZipList xs = ZipList (zipWith (\fx -> fx) fs xs) 


<*> , — , . . zipWith (\fx -> fx) fs xs . zipWith , .

pure . , . pure "haha" ZipList (["haha","haha","haha"... . , , pure , - . , - . , . , pure f <*> xs fmap f xs . pure 3 ZipList [3] , pure (*2) <*> ZipList [1,5,10] ZipList [2] , . , .

? Let's get a look. , ZipList a Show , getZipList :

 ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100,100] [101,102,103] ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100..] [101,102,103] ghci> getZipList $ max <$> ZipList [1,2,3,4,5,3] <*> ZipList [5,3,1,2] [5,3,3,4] ghci> getZipList $ (,,) <$> ZipList "dog" <*> ZipList "cat" <*> ZipList "rat" [('d','c','r'),('o','a','a'),('g','t','t')] 


: (,,) — , \xyz -> (x,y,z) . , (,) — , \xy -> (x,y) .

zipWith , zipWith3 , zipWith4 , 7 . zipWith , , . zipWith3 , , , . . , . , .



, . , pure f <*> x = fmap fx . . :



, . , , .



Control.Applicative , liftA2 :

 liftA2 :: (Applicative f) => (a -> b -> c) -> fa -> fb -> fc 


:

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


, , . , .

. . (a -> b -> c) -> (fa -> fb -> fc) . , , liftA2 , .

: , . , Just 3 Just 4 . , , :

 ghci> fmap (\x -> [x]) (Just 4) Just [4] 


, , Just 3 Just [4] . Just [3,4] ? :

 ghci> liftA2 (:) (Just 3) (Just [4]) Just [3,4] ghci> (:) <$> Just 3 <*> Just [4] Just [3,4] 


, : — , . , Just [3,4] , Just 2 , Just [2,3,4] ? , . , , .

, , . sequenceA :

 sequenceA :: (Applicative f) => [fa] -> f [a] sequenceA [] = pure [] sequenceA (x:xs) = (:) <$> x <*> sequenceA xs 


, ! . . . , . . (, x — , xs — , ), sequenceA , . , x , , , !

, :

 sequenceA [Just 1, Just 2] 


:

 (:) <$> Just 1 <*> sequenceA [Just 2] 


, :

 (:) <$> Just 1 <*> ((:) <$> Just 2 <*> sequenceA []) 


, sequenceA [] Just [] , :

 (:) <$> Just 1 <*> ((:) <$> Just 2 <*> Just []) 


:

 (:) <$> Just 1 <*> Just [2] 


Just [1,2] !

sequenceA — . , , , :

 sequenceA :: (Applicative f) => [fa] -> f [a] sequenceA = foldr (liftA2 (:)) (pure []) 


, , pure [] . liftA2 (:) , , . liftA2 (:) , . ., , , .

- :

 ghci> sequenceA [Just 3, Just 2, Just 1] Just [3,2,1] ghci> sequenceA [Just 3, Nothing, Just 1] Nothing ghci> sequenceA [(+3),(+2),(+1)] 3 [6,5,4] ghci> sequenceA [[1,2,3],[4,5,6]] [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] ghci> sequenceA [[1,2,3],[4,5,6],[3,4,4],[]] [] 


Maybe , sequenceA Maybe , . Nothing , Nothing . , Maybe , Nothing .

, sequenceA , . , , . sequenceA [(+3),(+2),(+1)] 3 (+3) 3 , (+2) — 3 , (+1) — 3 , .

(+) <$> (+3) <*> (*2) , , (+3) (*2) , + . , , sequenceA [(+3),(*2)] , . + : pure [] , .

sequenceA , , . , , . :

 ghci> map (\f -> f 7) [(>4),(<10),odd] [True,True,True] ghci> and $ map (\f -> f 7) [(>4),(<10),odd] True 


, and Bool True , True . — sequenceA :

 ghci> sequenceA [(>4),(<10),odd] 7 [True,True,True] ghci> and $ sequenceA [(>4),(<10),odd] 7 True 


sequenceA [(>4),(<10),odd] , [(>4),(<10),odd] , . (Num a) => [a -> Bool] (Num a) => a -> [Bool] . , ?

, , . [ord, (+3)] , ord , (+3) .

[] , sequenceA . , . , , sequenceAand then executed using a list generator:

 ghci> sequenceA [[1,2,3],[4,5,6]] [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] ghci> [[x,y] | x <- [1,2,3], y <- [4,5,6]] [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] ghci> sequenceA [[1,2],[3,4]] [[1,3],[1,4],[2,3],[2,4]] ghci> [[x,y] | x <- [1,2], y <- [3,4]] [[1,3],[1,4],[2,3],[2,4]] ghci> sequenceA [[1,2],[3,4],[5,6]] [[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]] ghci> [[x,y,z] | x <- [1,2], y <- [3,4], z <- [5,6]] [[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]] 


(+) <$> [1,2] <*> [4,5,6] x + y , x [1,2] , y [4,5,6] . , . , sequenceA [[1,2],[3,4],[5,6]] , [x,y,z] , x [1,2] , y [3,4] . . , . .

-, sequenceA — , sequence ! - -, -. , [IO a] IO [a] , -, , - , , . -, .

- getLine :

 ghci> sequenceA [getLine, getLine, getLine] heyh ho woo ["heyh","ho","woo"] 


, , . — , , -, , , , . ., — . <$> <*> , , .


, ( ):
  1. Introduction
  2. Start
  3. Modules
  4. -


— . . Thank!

Source: https://habr.com/ru/post/123767/


All Articles