本文主要是介绍Applicative与Arrow,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
原文说得非常清楚,不翻译效果可能更好。
进入ghci
> :m + Control.Applicative Control.Arrow
Control.Applicative Control.Arrow>filter ((>2) &&& (<7) >>> uncurry (&&)) [1..10]
[3,4,5,6]
We have two mysterious operators here, with the following types:
(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c')
(>>>) :: Category cat => cat a b -> cat b c -> cat a c
The Arrow and Category type classes are mostly about things that behave like functions, which of course includes functions themselves, and both instances here are just plain(->). So, rewriting the types to use that:
(&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c'))
(>>>) :: (a -> b) -> (b -> c) -> (a -> c)
The second has a very similar type to (.), the familiar function composition operator; in fact, they're the same, just with arguments swapped. The first is more unfamiliar, but the types again tell you all you need to know--it takes two functions, both taking an argument of a common type, and produces a single function that gives the results from both combined into a tuple.
So, the expression (>2) &&& (<7) takes a single number and produces a pair of Bool values based on the comparisons. The result of this is then fed into uncurry (&&), which just takes a pair of Bools and ANDs them together. The resulting predicate is used to filter the list in the usual manner.
Control.Applicative Control.Arrow>filter ((&&) <$> (>2) <*> (<7)) [1..10]
[3,4,5,6]
We have two mysterious operators, again, with the following types:
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Observe that the second argument of (<$>) in this case is (>2), which has type (Ord a, Num a) => a -> Bool, while the type of (<$>)'s argument has type f a. How are these compatible?
The answer is that, just as we could substitute (->) for a and cat in the earlier type signatures, we can think of a -> Bool as (->) a Bool, and substitute((->) a) for the f. So, rewriting the types, using ((->) t) instead to avoid clashing with the other type variable a:
(<$>) :: (a -> b) -> ((->) t) a -> ((->) t) b
(<*>) :: ((->) t) (a -> b) -> ((->) t) a -> ((->) t) b
Now, putting things back in normal infix form:
(<$>) :: (a -> b) -> (t -> a) -> (t -> b)
(<*>) :: (t -> (a -> b)) -> (t -> a) -> (t -> b)
The first turns out to be function composition, as you can observe from the types. The second is more complicated, but once more the types tell you what you need--it takes two functions with an argument of a common type,one producing a function, the other producing an argument to pass to the function. In other words, something like \f g x -> f x (g x). (This function also happens to be known as the S combinator in combinatory logic, a subject explored extensively by the logician Haskell Curry, whose name no doubt seems strangely familiar!)
The combination of (<$>) and (<*>) sort of "extends" what (<$>) alone does, which in this case means taking a function with two arguments, two functions with a common argument type, applying a single value to the latter two, then applying the first function to the two results. So ((&&) <$> (>2) <*> (<7)) x simplifies to(&&) ((>2) x) ((<7) x), or using normal infix style, x > 2 && x < 7. As before, the compound expression is used to filter the list in the usual manner.
Also, note that while both functions are obfuscated to some degree, once you get used to the operators used, they're actually quite readable. The first abstracts over a compound expression doing multiple things to a single argument, while the second is a generalized form of the standard "pipeline" style of stringing things together with function composition.
Personally I actually find the first one perfectly readable. But I don't expect most people to agree!
总结
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"
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.
这篇关于Applicative与Arrow的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!