1

I'm pretty sure this has a simple solution but it eludes me and I cannot seem to find a straight answer.

Normally, when applying liftA2 assuming the binary function has already been lifted once, the signature looks like this:

liftA2'   :: (Applicative f1, Applicative f) 
             => (f a -> f b -> f c) -> f1 (f a) -> f1 (f b) -> f1 (f c)

Is it possible to apply the "inverse" of, for example liftA2 such as:

inverseA2 :: (Applicative f, Applicative f1) 
             => (f a -> f b -> f c) -> f (f1 a) -> f (f1 b) -> f (f1 c)

As a concrete example, I would like to obtain the function:

f :: ([a] -> [b] -> [c]) -> [Maybe a] -> [Maybe b] -> [Maybe c]

One way would be to resort to "pack" each argument [Maybe a] -> Maybe [a] and "unpack" Maybe [a] -> [Maybe a] the result of applying a normal liftA2. I would like to avoid that since, as you can imagine, packing is destructive (e.g. pack [Just 1, Nothing, Just 2] == Nothing ).


Update: as @user2407038 pointed out, in order for f to apply the given function you necessarily need a function along the lines of [Maybe a] -> [a] which does lose information. So for these two particular functors there is no apparent way to satisfy the additional requirement posed. But for any other two functors f, f1 which have an invertible function forall a . f a -> f1 a the answer accepted fits perfectly as a solution to this question.

Iedu
  • 117
  • 8
  • Take `f :: [Int] -> [Char] -> [Int] ; f x y = [length x + length y]`. What would you expect from `inverseA2 f :: [Maybe Int] -> [Maybe Char] -> [Maybe Int]` ? What about `f x y = [length x + length y, head x]`? I'm not sure what the general definition would be... – chi Jul 21 '16 at 11:19
  • 2
    In order for `f` to apply the given function, it must produce a `[a]` from a `[Maybe a]` (naturally there is no way to get `[a]` from `[Maybe b]` since there is no way to get `a` from `b`) and so you necessarily *need* a function `[Maybe a] -> [a]` - and as you've noted this function necessarily loses information. The same is true of the general case - there are few functors `f1, f2` which have an invertible function `forall a . f1 a -> f2 a`. – user2407038 Jul 21 '16 at 12:47
  • @user2407038 so there was a reason I did not find a straightforward answer, since I was looking at the wrong problem. Thanks for pointing out the invertible functions! I will look more into this. – Iedu Jul 21 '16 at 13:02
  • @chi to clarify the situation with your example, let's say that I get the function `f` already defined as `f :: [Int] -> [Char] -> [Int]` and I would like to apply it to the respective structures `a = [Nothing, Just 1, Just 3]` and `b = [Just 'a', Just 'b']`. In this case the first function should return `inverseA2 f a b == [Just 5]`, while the second should be `inverseA2 f a b == [Just 5, Nothing]`. But using the pack/unpack method grants `[Nothing]` for the first function and `[Nothing]` for the second one respectively. – Iedu Jul 21 '16 at 13:45

1 Answers1

1

I'm sure you've probably figured this out, but I don't think you can do this with the constraints you have. If you are a bit more liberal with your constraints, you'll get something though....

inverseA2 :: (Applicative f, Traversable f, Applicative f1, Traversable f1)
               => (f a -> f b -> f c) -> f (f1 a) -> f (f1 b) -> f (f1 c)
inverseA2 f x y = sequenceA (liftA2 f (sequenceA x) (sequenceA y))

The only reason I'm putting this up is that for your particular example with Maybe and [], these constraints are all satisfied, so doing this is possible for that case. Still not settling at all though.

You could also try experimenting with writing your own instances for Data.Distributive giving you distribute, which is similar to sequenceA...

Edited to include @dfeuer's suggestions.

Alec
  • 31,829
  • 7
  • 67
  • 114
  • Very elegant! `sequence` describes perfectly what `pack` and `unpack` are doing, thanks for pointing it out (now I realized I am speaking gibberish when I talk about transposing, I will correct that)! Unfortunately, as you said, it is implementing the case I am trying to avoid. As in the example above, evaluating `inverseM2 (\x y = [length x + length y]) [Nothing, Just 1, Just 3] [Just 'a', Just 'b']` will return `[Nothing]` instead of `[Just 5]` – Iedu Jul 21 '16 at 19:47
  • 2
    `Monad` is clearly overkill (just replace `sequence` with `sequenceA`). Also, I would speculate that you could use several combinations of `Traversable` and `Distributive` for context. – dfeuer Jul 21 '16 at 20:54
  • Although the resources you provide are invaluable, I still hit the problem of "sequencing" the input arguments (i.e. `[Maybe a] -> Maybe [a]`). For these two particular functors I cannot find such a non-destructive transformation, since there is no way of describing `Nothing` as a value in the set `a`. So I guess I should accept the situation as it is. – Iedu Jul 22 '16 at 11:41