简体   繁体   中英

Does the Applicative interface provide power beyond the ability to lift multi-argument functions (in curried form) into a Functor?

Applicatives are often presented as a way to lift multi-argument functions into a functor and apply functor values to it. But I wonder if there is some subtle additional power stemming from the fact that it can do so by lifting functions that return a function and applying the function arguments one at a time.

Imagine instead we define an interface based on lifting functions whose argument is a tuple of arguments:

# from Functor
fmap :: (a -> b) -> Fa -> Fb
# from Applicative
pure :: a -> Fa

# combine multiple functor values into a functor of a tuple
tuple1 :: Fa -> F(a)
tuple2 :: Fa -> Fb -> F(a,b)
tuple3 :: Fa -> Fb -> Fc -> F(a,b,c)
(etc ...)

# lift multi-argument functions (that take a tuple as input)
ap_tuple1 :: ((a) -> b) -> F(a) -> Fb
ap_tuple2 :: ((a,b) -> c) -> F(a,b) -> Fc
ap_tuple3 :: ((a,b,c) -> d) -> F(a,b,c) -> Fd
(etc ..)

Assume we had the corresponding tuple function defined for every sized tuple we may encounter. Would this interface be equally as powerful as the Applicative interface, given it allows for lifting/applying-to multi-argument functions BUT doesn't allow for lifting/applying-to functions that return a function? Obviously one can curry functions that take a tuple as an argument so they can be lifted in an applicative and one can uncurry functions that return a function in order to lift them into hypothetical implementation above. But to my mind there is a subtle difference in power. Is there any difference? (Assuming the question even makes sense)

You've rediscovered the monoidal presentation of Applicative . It looks like this:

class Functor f => Monoidal f where
    (>*<) :: f a -> f b -> f (a, b)
    unit :: f ()

It's isomorphic to Applicative via:

(>*<) = liftA2 (,)
unit = pure ()

pure x = x <$ unit
f <*> x = fmap (uncurry ($)) (f >*< x)

By the way, your ap_tuple functions are all just fmap . The "hard" part with multiple values is combining them together. Splitting them back into pieces is "easy".

Yes, this is equally as powerful. Notice that pure and tuple1 are the same. Further, everything higher than tuple2 is recovered from tuple2 and fmap :

tuple3 x y z = repair <$> tuple2 (tuple2 x y) z
    where repair ((a, b), c) = (a, b, c)
tuple4 w x y z = repair <$> tuple2 (tuple2 x y) (tuple2 x y)
    where repair ((a, b), (c, d)) = (a, b, c, d)
-- etc.

Also, all of the ap_tuple s are just fmap :

ap_tuple1 = fmap
ap_tuple2 = fmap
ap_tuple3 = fmap
-- ...

Renaming prod = tuple2 , your question boils down to

Is

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

equivalent to

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

?

And you might already see that the answer is yes. prod is just a specialization of liftA2

prod = liftA2 (,)

But (,) is "natural" in the sense that it doesn't "delete" anything, so you can recover liftA2 just by destructuring the data back out:

liftA2 f x y = f' <$> prod x y
    where f' (a, b) = f a 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