简体   繁体   中英

what does (<*>) :: f (a -> b) -> f a -> f b exactly do in the Functor class

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

From my understanding, it takes a function f, where another function (a -> b) as its argument, returns a function f . Applying f to a then returns a function f and apply f to b .

Here is an example:

Prelude> (+) <$> Just 2 <*> Just 3
Just 5

But I don't quite understand how it works.

I guess (+) should be f , Just 2 and Just 3 should be a and b respectively. Then what is (a -> b) ?

From my understanding, it takes a function f...

Unfortunately this is incorrect. In this case, f is a type, not a function. Specifically, f is a "higher-kinded type" with kind * -> * . The type f is the functor.

In this case, f is Maybe . So we can rewrite the function types, specializing them for Maybe :

pure :: a -> Maybe a
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b

It starts to become a bit clearer once you get this far. There are a couple different possible definitions for pure , but only one that makes sense:

pure = Just

The operator x <$> y is the same as pure x <*> y , so if you write out:

(+) <$> Just 2 <*> Just 3

Then we can rewrite it as:

pure (+) <*> pure 2 <*> pure 3

Although this technically has a more general type. Working with the functor laws, we know that pure x <*> pure y is the same as pure (xy) , so we get

pure ((+) 2) <*> pure 3
pure ((+) 2 3)
pure (2 + 3)

In this case, we a and b are the types but since <*> appears twice they actually have different types in each case.

In the first <*> , a is Int and b is Int -> Int . In the second <*> , both a and b are Int . (Technically you get generic versions of Int but that's not really important to the question.)

Applicative functors were introduced to Haskell as applicative style programming "Idioms" . Unpacking this phrase, we have "applicative style programming"; which is just application of functions to arguments. We also have "idioms", or phrases in a language which have a special meaning. For example "raining cats and dogs" is an idiom for raining very heavily. Putting them together, applicative functors are function applications with special meaning.

Take for example, following Dietrich Epp's lead, anApplication defined by a function,

anApplication = f a
  where
    f = (+2)
    a = 3

and, anIdiomaticApplication , defined with idiomatic application,

anIdiomaticApplication = f <*> a
   where 
     f = Just (+2)
     a = Just 3

The top level structure of these definitions are similar. The difference? The first has a space--normal function application--and the second has <*> --idiomatic function application. This illustrates how <*> facilitates applicative style: just use <*> in place of a space.

The application, <*> , is idiomatic because it carries a meaning other than just pure function application. By way of exposition, in anIdiomaticApplication we have something like this:

 f <*> a :: Maybe (Int -> Int) <*> Maybe Int

Here, the <*> in the type is used to represent a type level function* that corresponds to the signature of the real <*> . To the type- <*> we apply the type arguments for f and a (respectively Maybe (Int -> Int) and Maybe Int ). After application we have

 f <*> a :: Maybe Int

As an intermediate step, we can imagine something like

 f <*> a :: Maybe ((Int -> Int) _ Int)

With _ being the type level stand-in for regular function application.

At this point we can finally see the idiom-ness called out. f <*> a is like a normal function application, (Int -> Int) _ Int , in the Maybe context/idiom. So, <*> is just function application that happens within a certain context.

In parting, I'll emphasize that understanding <*> is only partially understanding its use. We can understand that f <*> a is just function application which some extra idiomatic meaning. Due to the Applicative laws, we can also assume that idiomatic application will be somehow sensible.

Don't be surprised, however, if you look at <*> and get confused since there is so little there. We must also be versed in the various Haskell Idioms. For instance, in the Maybe idiom either the function or value may not be present, in which case the output will be Nothing . There are of course, many others, but getting familiar with just Either a and State s should model a wide variety of the different kinds.

*Something like this could actually be made with a closed type family (untested)

type family IdmApp f a where
   IdmApp (f (a->b)) a = f b

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM