well-typed / generics-sop

Generic Programming using True Sums of Products
BSD 3-Clause "New" or "Revised" License
157 stars 48 forks source link

Map between structures with different types #134

Closed AriFordsham closed 3 years ago

AriFordsham commented 3 years ago

Can generics-sop be used to map between structures of the same shape but different (but related) types?

Imagine I had a record

data Record = Record {
  foo :: Int,
  bar :: String
}

Rep Record is SOP I '[ '[Int, String] ].

I want to map this to SOP I '[ '[Maybe Int, Maybe String] ] by wrapping each field in Just. How would I do this with generics-sop?

All the combinators seem to take functions a -> a.

edsko commented 3 years ago

You don't want to go to SOP I, but rather to SOP Maybe. hmap Just . from should do it.

AriFordsham commented 3 years ago

hmap (Just . unI) . from

AriFordsham commented 3 years ago

Can this be used with type families or fundeps?

Given:

class C1 a where
    type F a

f1 :: (C1 a) => a -> F a
f1 = undefined 

class C2 b a | a -> b where

f2 :: (C2 b a) => a -> b
f2 = undefined 

Neither of these compile:

c1 :: (Generic a, C1 a) => a -> SOP f' (Code a)
c1 = hmap (f1 . unI) . from
c2 = hmap (f2 . unI) . from
kosmikus commented 3 years ago

What is f' in the lower code block?

There seem to be several problems here. Let's focus on the first example (f1 / c1). In c1, you are first applying from. At that point you have a SOP I (Code a). In order to now map f1 . unI, you need to know that all the elements of Code a are members of class C1. Whether a itself is is irrelevant. So you need to apply hcmap. Furthermore, you'd then get an SOP F (Code a) as a result, but that doesn't work, because type families cannot be partially applied. So you'd either need to make the type family into a data family, or wrap the F-application in a newtype.

So this would work, for example:

{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
module T134 where

import Generics.SOP

class C1 a where
  type F a

f1 :: C1 a => a -> F a
f1 = undefined

newtype WrapF a = WrapF (F a)

c1 :: (Generic a, All2 C1 (Code a)) => a -> SOP WrapF (Code a)
c1 = hcmap (Proxy @C1) (WrapF . f1 . unI) . from

The second example is more difficult, because there's no direct relationship between a and b except as given by C2. While this can in principle still be made to work (via htrans), it's almost certainly not the right approach.

It'd be good to know what you are actually trying to achieve.