What are practical uses of applicative style?

后端 未结 11 660
误落风尘
误落风尘 2021-01-30 01:32

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 an

相关标签:
11条回答
  • 2021-01-30 01:54

    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.

    0 讨论(0)
  • 2021-01-30 01:55

    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.

    0 讨论(0)
  • 2021-01-30 01:55

    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.

    0 讨论(0)
  • 2021-01-30 01:59

    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

    0 讨论(0)
  • 2021-01-30 02:02

    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 :)

    0 讨论(0)
  • 2021-01-30 02:05

    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.

    0 讨论(0)
提交回复
热议问题