Consider this type:
data Vec3 = Vec3 {_x, _y, _z :: Int}
I have some functions that all take the same input, and may fail to compute a field:
data Input
getX, getY, getZ :: Input -> Maybe Int
I can write a function that tries all three of these field constructors:
v3 :: Input -> Maybe Vec3
v3 i = liftA3 Vec3 (getX i) (getY i) (getZ y)
But it's kinda annoying to have to pass that i input around three times. Functions are themselves an applicative functor, and so one can replace foo x = bar (f x) (g x) (h x) with foo = liftA3 bar f g h.
Here, my bar is liftA3 Vec3, so I could write
v3' :: Input -> Maybe Vec3
v3' = liftA3 (liftA3 Vec3) getX getY getZ
But this is a bit gross, and when we work with composed applicatives in this way (((->) Input) and Maybe), there's the Compose newtype to handle this kind of thing. With it, I can write
v3'' :: Input -> Maybe Vec3
v3'' = getCompose go
where go = liftA3 Vec3 x y z
x = Compose getX
y = Compose getY
z = Compose getZ
Okay, not exactly a great character savings, but we're now working with one combined functor instead of two, which is nice. And I thought I could use coerce to win me back some of the characters: after all, x is just a newtype wrapper around getX, and likewise for the other fields. So I thought I could coerce liftA3 into accepting three Input -> Maybe Vec3 instead of accepting three Compose ((->) Input) Maybe Vec3:
v3''' :: Input -> Maybe Vec3
v3''' = getCompose go
where go = coerce liftA3 Vec3 getX getY getZ
But this doesn't work, yielding the error message:
tmp.hs:23:14: error:
• Couldn't match representation of type ‘f0 c0’
with that of ‘Input -> Maybe Int’
arising from a use of ‘coerce’
• In the expression: coerce liftA3 Vec3 getX getY getZ
In an equation for ‘go’: go = coerce liftA3 Vec3 getX getY getZ
In an equation for ‘v3'''’:
v3'''
= getCompose go
where
go = coerce liftA3 Vec3 getX getY getZ
|
23 | where go = coerce liftA3 Vec3 getX getY getZ
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I don't understand why not. I can write coerce getX :: Compose ((->) Input) Maybe Int, and this is fine. And generally a function can be coerced in order to make it coerce its arguments or return type, as in
coerce ((+) :: Int -> Int -> Int) (Max (5::Int)) (Min (8::Int)) :: Sum Int
And I can in fact write out all the coerces individually:
v3'''' :: Input -> Maybe Vec3
v3'''' = getCompose go
where go = liftA3 Vec3 x y z
x = coerce getX
y = coerce getY
z = coerce getZ
So why can't liftA3 itself be coerced to accept getX instead of coerce getX, allowing me to use v3'''?