Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't `coerce` implicitly apply Compose to these functions?

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'''?

like image 840
amalloy Avatar asked Dec 07 '21 11:12

amalloy


People also ask

Why is it important for composable functions to be free of side effects?

Since a composable function can be executed multiple times or can skip execution altogether, mutating or relying on global state in composable functions can lead to unforeseen errors or bugs. Hence relying on side-effects is not the recommended approach.

Why should we use compose?

Compose enables you to create beautiful apps with direct access to the Android platform APIs and built-in support for Material Design, Dark theme, animations, and more: “Compose has also solved more than declarative UI -- accessibility apis, layout, all kinds of stuff have been improved.

What annotation is used to annotate composable functions?

The @Preview annotation lets you preview your composable functions within Android Studio without having to build and install the app to an Android device or emulator. The annotation must be used on a composable function that does not take in parameters.


1 Answers

If you provide the applicative functor to liftA3, then the following typechecks:

v3' :: Input -> Maybe Vec3
v3' = coerce (liftA3 @(Compose ((->) Input) Maybe) Vec3) getX getY getZ

In coerce liftA3 without any annotation, there is no way to infer what applicative functor to use liftA3 with. Neither of these even mention the type Compose. It might just as well be ReaderT Input Maybe, Kleisli Maybe Input, another type with an unlawful instance or something even more exotic.

In getCompose (coerce liftA3 _ _ _) (your last attempt), the getCompose does not constraint liftA3 ("inside" of coerce), because getCompose is "outside" of coerce. It requires that the result type of liftA3 is coercible to Compose ((->) Input) Maybe Vec3, but it might still not be equal to that.

like image 87
Li-yao Xia Avatar answered Oct 17 '22 18:10

Li-yao Xia