4

I want to learn to use Deriving Via for more types. Currently a problem I'm often struggling with is when the generic representations are different, but would be equal if the conversion to type representations went deeper into nested types.

Simple example:

coerce @(Either () ()) @Bool

doesn't work, as Rec0 () :+: Rec0 () doesn't coerce with U1 :+: U1, even though :kind! Rep () gives U1, too.

More complex example

data LeafTree a = Leaf a | Branch [LeafTree a]

is isomorphic to Free [] a and it doesn't coerce for similar reasons. How I can I coerce between these types? I also know how to use DerivingVia with coercing between types with equal Rep1 if that helps here.

Johannes Riecken
  • 2,301
  • 16
  • 17
  • 5
    I'd consider `Either () ()` and `Bool` to be only "morally equivalent". If we instead consider bottom values, the former has `_|_, Left _|_, Right _|_, Left (), Right ()` while the latter has `_|_, False, True`. I would not expect to be able to `coerce` between them -- they look like they must have a different runtime representation. – chi Jan 18 '22 at 16:47
  • @chi, indeed. The `Either` constructors hold pointers to their contents, even if the contents happen to be trivial. – dfeuer Jan 18 '22 at 18:47
  • Interesting point. In the context of DerivingVia I would however expect to be able to coerce, because bottoms are irrelevant for deciding which type class's laws a data type respects. Does that make sense? If it does, then I think I'll bring this up for discussion on the GHC issue tracker. – Johannes Riecken Jan 19 '22 at 10:12
  • 1
    @rubystallion, no, `coerce` and (therefore) `DerivingVia` are all about "representational equality" in a very strict sense. The types being coerced must have *exactly the same* representation in memory, and the (somewhat limited) coercion system has to be able to *prove* that. – dfeuer Jan 20 '22 at 05:15
  • 3
    Why is it like that? Well, the main point of `coerce` is that it *doesn't do anything*. It's completely erased when GHC lowers Core (its main intermediate language) to STG (another intermediate language), so it costs nothing at run-time. – dfeuer Jan 20 '22 at 05:21
  • Using a typeclass to replace `Rec0 a` with `Rep a`, and applying it with an `Unpack` newtype seems to work and optimize fine. I'm not sure if I would advocate this approach, though https://gist.github.com/Tarmean/04253cd8c32ad7623b20e4ea3336fd6a Note that `coerce` isn't directly tied to Generics, it is a noop at runtime while Generics do a real transformation. You can coerce the generic representation, though, as in the reddit post you linked. – Taren Jan 21 '22 at 09:47
  • @Taren Great! Do you want to post that as an answer so people see that my question (or what I wanted to ask) is solved? I'd also be interested in what you don't like about this approach. That it makes the optimizer's job harder? – Johannes Riecken Jan 21 '22 at 14:41

1 Answers1

1

Now with the iso-deriving package a monad instance could also be derived like this:

{-# LANGUAGE TypeOperators, FlexibleInstances, MultiParamTypeClasses, DeriveTraversable, DerivingVia #-}
import Control.Monad.Free
import Iso.Deriving

data LeafTree a = Leaf a | Branch [LeafTree a]
  deriving (Functor)
  deriving (Applicative, Monad) via Free [] `As1` LeafTree

instance Inject (Free [] a) (LeafTree a) where
  inj (Pure x ) = Leaf x
  inj (Free xs) = Branch (fmap inj xs)

instance Project (Free [] a) (LeafTree a) where
  prj (Leaf   x ) = Pure x
  prj (Branch xs) = Free (fmap prj xs)

instance Isomorphic (Free [] a) (LeafTree a)
Johannes Riecken
  • 2,301
  • 16
  • 17