Skip to content

Instantly share code, notes, and snippets.

@notae
Created June 8, 2018 09:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save notae/67473b53ed44f8e2227f51955f323b68 to your computer and use it in GitHub Desktop.
Save notae/67473b53ed44f8e2227f51955f323b68 to your computer and use it in GitHub Desktop.
Convert between isomorphic types with GHC.Generics
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module GenericIso
( GIso, GenericIso(..)
) where
import Data.Int
import Data.Word
import GHC.Generics
type GIso a b = (Generic a, Generic b, GIso' (Rep a) (Rep b))
class GenericIso a b where
giso :: a -> b
default giso :: a ~ b => a -> b
giso = id
-- | Non primitive types
instance {-# OVERLAPPABLE #-} GIso a b => GenericIso a b where
giso = to . giso' . from
-- | Primitive types
instance {-# OVERLAPPING #-} GenericIso () ()
instance {-# OVERLAPPING #-} GenericIso Bool Bool
instance {-# OVERLAPPING #-} GenericIso Char Char
instance {-# OVERLAPPING #-} GenericIso Integer Integer
instance {-# OVERLAPPING #-} GenericIso Int Int
instance {-# OVERLAPPING #-} GenericIso Int8 Int8
instance {-# OVERLAPPING #-} GenericIso Int16 Int16
instance {-# OVERLAPPING #-} GenericIso Int32 Int32
instance {-# OVERLAPPING #-} GenericIso Int64 Int64
instance {-# OVERLAPPING #-} GenericIso Word Word
instance {-# OVERLAPPING #-} GenericIso Word8 Word8
instance {-# OVERLAPPING #-} GenericIso Word16 Word16
instance {-# OVERLAPPING #-} GenericIso Word32 Word32
instance {-# OVERLAPPING #-} GenericIso Word64 Word64
instance {-# OVERLAPPING #-} GenericIso Float Float
instance {-# OVERLAPPING #-} GenericIso Double Double
-- | Internal class to convert type on generic representation
class GIso' f g where
giso' :: f a -> g a
instance GIso' V1 V1 where
giso' = id
instance GIso' U1 U1 where
giso' = id
instance GenericIso c d => GIso' (K1 i c) (K1 j d) where
giso' = K1 . giso . unK1
instance GIso' f g => GIso' (M1 i c f) (M1 j d g) where
giso' = M1 . giso' . unM1
instance (GIso' f1 f2, GIso' g1 g2)
=> GIso' (f1 :+: g1) (f2 :+: g2) where
giso' (L1 l) = L1 (giso' l)
giso' (R1 r) = R1 (giso' r)
instance (GIso' f1 f2, GIso' g1 g2)
=> GIso' (f1 :*: g1) (f2 :*: g2) where
giso' (l :*: r) = giso' l :*: giso' r
@treeowl
Copy link

treeowl commented Feb 26, 2021

I know this is just an old gist, but all this overlapping feels wrong. When a ~ b, I think you always want the default. That suggests something like the following.

type family Equal a b where
  Equal a a = 'True
  Equal _ _ = 'False

class Switcher (Equal a b) a b => GenericIso a b
instance Switcher (Equal a b) a b => GenericIso a b
genericIso :: GenericIso a b => a -> b
genericIso = giso

class Switcher (same :: Bool) a b where
  giso :: a -> b
instance a ~ b => Switcher 'True a b where
  giso = id
instance UserIso a b => Switcher 'False a b where
  giso = userIso

class UserIso a b where
  userIso :: a -> b
  default userIso :: GIso' a b => a -> b
  userIso = giso'

Now, all those same-type situations are handled automatically, and users can instantiate UserIso either manually or using the generic default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment