ChatGPT解决这个技术问题 Extra ChatGPT

What are practical uses of applicative style?

I am a Scala programmer, learning Haskell now. It's easy to find practical use cases and real world examples for OO concepts, such as decorators, strategy pattern etc. Books and interwebs are filled with it.

I came to the realization that this somehow is not the case for functional concepts. Case in point: applicatives.

I am struggling to find practical use cases for applicatives. Almost all of the tutorials and books I have come across so far provide the examples of [] and Maybe. I expected applicatives to be more applicable than that, seeing all the attention they get in the FP community.

I think I understand the conceptual basis for applicatives (maybe I am wrong), and I have waited long for my moment of enlightenment. But it doesn't seem to be happening. Never while programming, have I had a moment when I would shout with a joy, "Eureka! I can use applicative here!" (except again, for [] and Maybe).

Can someone please guide me how applicatives can be used in a day-to-day programming? How do I start spotting the pattern? Thanks!

First time I was inspired to learn that stuff by these two articles: debasishg.blogspot.com/2010/11/exploring-scalaz.html debasishg.blogspot.com/2011/02/…
The paper The Essence of the Iterator Pattern is all about how Applicative is the essence of the iterator pattern.

d
duplode

Applicatives are great when you've got a plain old function of several variables, and you have the arguments but they're wrapped up in some kind of context. For instance, you have the plain old concatenate function (++) but you want to apply it to 2 strings which were acquired through I/O. Then the fact that IO is an applicative functor comes to the rescue:

Prelude Control.Applicative> (++) <$> getLine <*> getLine
hi
there
"hithere"

Even though you explicitly asked for non-Maybe examples, it seems like a great use case to me, so I'll give an example. You have a regular function of several variables, but you don't know if you have all the values you need (some of them may have failed to compute, yielding Nothing). So essentially because you have "partial values", you want to turn your function into a partial function, which is undefined if any of its inputs is undefined. Then

Prelude Control.Applicative> (+) <$> Just 3 <*> Just 5
Just 8

but

Prelude Control.Applicative> (+) <$> Just 3 <*> Nothing
Nothing

which is exactly what you want.

The basic idea is that you're "lifting" a regular function into a context where it can be applied to as many arguments as you like. The extra power of Applicative over just a basic Functor is that it can lift functions of arbitrary arity, whereas fmap can only lift a unary function.


I'm not sure the applicative IO example is a good one, as applicative isn't so much concerned about the ordering imho, but in (| (++) getLine getLine |) the ordering of the two getLine actions becomes significant for the outcome...
@hvr: Which order (<*>) sequences things in is arbitrary, but is usually left-to-right by convention, such that f <$> x <*> y == do { x' <- x; y' <- y; return (f x y) }
@hvr: Well, keep in mind that in the expression itself can't depend on the sequencing, because the lifted function can't observe the difference and both effects will occur no matter what. Which order is chosen is defined by the instance alone, which should know which is correct. Also, do note that the documentation specifies that for Monad instances, (<*>) = ap, which fixes the ordering to match my example above.
The <$> and <*> style operators are declared "infixl 4" so there is no ambiguous convention, it is specified with the declaration that it will group/associate left to right. The r2l or l2r order of effects is still controlled by the actual instance, which for monads uses the same order as "Control.Monad.ap" which is "liftM2 id" and liftM2 is documented to run left to right.
@Chris, grouping left to right does not have anything to do with executing left to right though.
d
duplode

Since many applicatives are also monads, I feel there's really two sides to this question.

Why would I want to use the applicative interface instead of the monadic one when both are available?

This is mostly a matter of style. Although monads have the syntactic sugar of do-notation, using applicative style frequently leads to more compact code.

In this example, we have a type Foo and we want to construct random values of this type. Using the monad instance for IO, we might write

data Foo = Foo Int Double

randomFoo = do
    x <- randomIO
    y <- randomIO
    return $ Foo x y

The applicative variant is quite a bit shorter.

randomFoo = Foo <$> randomIO <*> randomIO

Of course, we could use liftM2 to get similar brevity, however the applicative style is neater than having to rely on arity-specific lifting functions.

In practice, I mostly find myself using applicatives much in the same way like I use point-free style: To avoid naming intermediate values when an operation is more clearly expressed as a composition of other operations.

Why would I want to use an applicative that is not a monad?

Since applicatives are more restricted than monads, this means that you can extract more useful static information about them.

An example of this is applicative parsers. Whereas monadic parsers support sequential composition using (>>=) :: Monad m => m a -> (a -> m b) -> m b, applicative parsers only use (<*>) :: Applicative f => f (a -> b) -> f a -> f b. The types make the difference obvious: In monadic parsers the grammar can change depending on the input, whereas in an applicative parser the grammar is fixed.

By limiting the interface in this way, we can for example determine whether a parser will accept the empty string without running it. We can also determine the first and follow sets, which can be used for optimization, or, as I've been playing with recently, constructing parsers that support better error recovery.


iinm, the recently re-added monad comprehensions in ghc give almost the same level of compactness as applicative combinators: [Foo x y | x <- randomIO, y <- randomIO]
@Dan: that's certainly shorter than the 'do' example, but it's still not point-free, which seems to be desirable in the Haskell world
I
IttayD

I think of Functor, Applicative and Monad as design patterns.

Imagine you want to write a Future[T] class. That is, a class that holds values that are to be calculated.

In a Java mindset, you might create it like

trait Future[T] {
  def get: T
}

Where 'get' blocks until the value is available.

You might realize this, and rewrite it to take a callback:

trait Future[T] {
  def foreach(f: T => Unit): Unit
}

But then what happens if there are two uses for the future? It means you need to keep a list of callbacks. Also, what happens if a method receives a Future[Int] and needs to return a calculation based on the Int inside? Or what do you do if you have two futures and you need to calculate something based on the values they will provide?

But if you know of FP concepts, you know that instead of working directly on T, you can manipulate the Future instance.

trait Future[T] {
  def map[U](f: T => U): Future[U]
}

Now your application changes so that each time you need to work on the contained value, you just return a new Future.

Once you start in this path, you can't stop there. You realize that in order to manipulate two futures, you just need to model as an applicative, in order to create futures, you need a monad definition for future, etc.

UPDATE: As suggested by @Eric, I've written a blog post: http://www.tikalk.com/incubator/blog/functional-programming-scala-rest-us


That's an interesting way to introduce Functor, Applicatives and Monads, well-worth a full blog post showing the details behind 'etc...'.
Link seems broken as of today. Wayback machine link is web.archive.org/web/20140604075710/http://www.tikalk.com/…
M
Max

I finally understood how applicatives can help in day-to-day programming with that presentation:

https://web.archive.org/web/20100818221025/http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-html/index.html

The autor shows how applicatives can help for combining validations and handling failures.

The presentation is in Scala, but the author also provides the full code example for Haskell, Java and C#.


The link is broken unfortunately.
D
Dan Burton

Warning: my answer is rather preachy/apologetic. So sue me.

Well, how often in your day-to-day Haskell programming do you create new data types? Sounds like you want to know when to make your own Applicative instance, and in all honesty unless you are rolling your own parser, you probably won't need to do it very much. Using applicative instances, on the other hand, you should learn to do frequently.

Applicative is not a "design pattern" like decorators or strategies. It is an abstraction, which makes it much more pervasive and generally useful, but much less tangible. The reason you have a hard time finding "practical uses" is because the example uses for it are almost too simple. You use decorators to put scrollbars on windows. You use strategies to unify the interface for both aggressive and defensive moves for your chess bot. But what are applicatives for? Well, they're a lot more generalized, so it's hard to say what they are for, and that's OK. Applicatives are handy as parsing combinators; the Yesod web framework uses Applicative to help set up and extract information from forms. If you look, you'll find a million and one uses for Applicative; it's all over the place. But since it's so abstract, you just need to get the feel for it in order to recognize the many places where it can help make your life easier.


I am surprised that this answer received a check mark while several other answers such as hammar's and oliver's are far down the page. I suggest those are superior because they provide excellent examples of applicatives outside of Maybe and []. Telling the questioner to think a little deeper simply not helpful.
@darrint - apparently the questioner did find it helpful, since he is the one that marked it as accepted. I stand by what I said: if one spends time playing around, even with just [] and Maybe instances, one will get a feel for what shape Applicative has and how it is used. This is what makes any typeclass useful: not necessarily knowing exactly what each instance does, but rather, having a general idea of what Applicative combinators do in general, so when you come across a new data type, and you learn it has an Applicative instance, you can start using it right away.
d
duplode

I think Applicatives ease the general usage of monadic code. How many times have you had the situation that you wanted to apply a function but the function was not monadic and the value you want to apply it to is monadic? For me: quite a lot of times! Here is an example that I just wrote yesterday:

ghci> import Data.Time.Clock
ghci> import Data.Time.Calendar
ghci> getCurrentTime >>= return . toGregorian . utctDay

in comparison to this using Applicative:

ghci> import Control.Applicative
ghci> toGregorian . utctDay <$> getCurrentTime

This form looks "more natural" (at least to my eyes :)


Actually, <$> is just fmap, it's re-exported from Data.Functor.
@Sjoerd Visscher: correct... The usage of <$> is still more appealing since fmap is not an infix operator per default. So it would have to be more like this: fmap (toGregorian . utctDay) getCurrentTime
The problem with fmap is that it doesn't work when you want to apply a plain function of multiple arguments to multiple monadic values; solving this is where Applicative proper comes in.
@oliver I think what Sjoerd was saying is that what you are showing is not really an example of where applicatives are useful, since you're really only dealing with a functor. It does showcase how applicative style is useful though.
C
Chris Kuklewicz

Coming at Applicative from "Functor" it generalizes "fmap" to easily express acting on several arguments (liftA2) or a sequence of arguments (using <*>).

Coming at Applicative from "Monad" it does not let the computation depend on the value that is computed. Specifically you cannot pattern match and branch on a returned value, typically all you can do is pass it to another constructor or function.

Thus I see Applicative as sandwiched in between Functor and Monad. Recognizing when you are not branching on the values from a monadic computation is one way to see when to switch to Applicative.


d
duplode

Here is an example taken from the aeson package:

data Coord = Coord { x :: Double, y :: Double }

instance FromJSON Coord where
   parseJSON (Object v) = 
      Coord <$>
        v .: "x" <*>
        v .: "y"

S
Sukant Hajra

There are some ADTs like ZipList that can have applicative instances, but not monadic instances. This was a very helpful example for me when understanding the difference between applicatives and monads. Since so many applicatives are also monads, it's easy to not see the difference between the two without a concrete example like ZipList.


A
Artyom Shalkhakov

I think it might be worthwhile to browse the sources of packages on Hackage, and see first-handedly how applicative functors and the like are used in existing Haskell code.


Either a specific link or more details would be worth adding here.
S
Shelby Moore III

I described an example of practical use of the applicative functor in a discussion, which I quote below.

Note the code examples are pseudo-code for my hypothetical language which would hide the type classes in a conceptual form of subtyping, so if you see a method call for apply just translate into your type class model, e.g. <*> in Scalaz or Haskell.

If we mark elements of an array or hashmap with null or none to indicate their index or key is valid yet valueless, the Applicative enables without any boilerplate skipping the valueless elements while applying operations to the elements that have a value. And more importantly it can automatically handle any Wrapped semantics that are unknown a priori, i.e. operations on T over Hashmap[Wrapped[T]] (any over any level of composition, e.g. Hashmap[Wrapped[Wrapped2[T]]] because applicative is composable but monad is not). I can already picture how it will make my code easier to understand. I can focus on the semantics, not on all the cruft to get me there and my semantics will be open under extension of Wrapped whereas all your example code isn’t. Significantly, I forgot to point out before that your prior examples do not emulate the return value of the Applicative, which will be a List, not a Nullable, Option, or Maybe. So even my attempts to repair your examples were not emulating Applicative.apply. Remember the functionToApply is the input to the Applicative.apply, so the container maintains control. list1.apply( list2.apply( ... listN.apply( List.lift(functionToApply) ) ... ) ) Equivalently. list1.apply( list2.apply( ... listN.map(functionToApply) ... ) ) And my proposed syntactical sugar which the compiler would translate to the above. funcToApply(list1, list2, ... list N)

It is useful to read that interactive discussion, because I can't copy it all here. I expect that url to not break, given who the owner of that blog is. For example, I quote from further down the discussion.

the conflation of out-of-statement control flow with assignment is probably not desired by most programmers Applicative.apply is for generalizing the partial application of functions to parameterized types (a.k.a. generics) at any level of nesting (composition) of the type parameter. This is all about making more generalized composition possible. The generality can’t be accomplished by pulling it outside the completed evaluation (i.e. return value) of the function, analogous to the onion can’t be peeled from the inside-out. Thus it isn’t conflation, it is a new degree-of-freedom that is not currently available to you. Per our discussion up thread, this is why you must throw exceptions or stored them in a global variable, because your language doesn’t have this degree-of-freedom. And that is not the only application of these category theory functors (expounded in my comment in moderator queue). I provided a link to an example abstracting validation in Scala, F#, and C#, which is currently stuck in moderator queue. Compare the obnoxious C# version of the code. And the reason is because the C# is not generalized. I intuitively expect that C# case-specific boilerplate will explode geometrically as the program grows.