Skip to content

Instantly share code, notes, and snippets.

@RyanGlScott
Created December 20, 2016 02:02
Show Gist options
  • Save RyanGlScott/f036622053a6874d7a06b353dc366da6 to your computer and use it in GitHub Desktop.
Save RyanGlScott/f036622053a6874d7a06b353dc366da6 to your computer and use it in GitHub Desktop.
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators #-}
module GenericIso where
import Data.Coerce
import GHC.Generics
class Convertible a b where
convert :: a -> b
default convert :: (Generic a, Generic b, GConvertible (Rep a) (Rep b))
=> a -> b
convert = to . gconvert . from
instance Convertible a a where
convert = id
class GConvertible f g where
gconvert :: f a -> g a
instance GConvertible V1 V1 where
gconvert = id
instance GConvertible U1 U1 where
gconvert = id
instance Convertible c d => GConvertible (K1 i c) (K1 j d) where
gconvert = K1 . convert . unK1
instance GConvertible f g => GConvertible (M1 i c f) (M1 j d g) where
gconvert = M1 . gconvert . unM1
instance (GConvertible f1 f2, GConvertible g1 g2)
=> GConvertible (f1 :+: g1) (f2 :+: g2) where
gconvert (L1 l) = L1 (gconvert l)
gconvert (R1 r) = R1 (gconvert r)
instance (GConvertible f1 f2, GConvertible g1 g2)
=> GConvertible (f1 :*: g1) (f2 :*: g2) where
gconvert (l :*: r) = gconvert l :*: gconvert r
--------------
-- Examples --
--------------
data A = A String Int deriving Generic
data B = B String Int deriving Generic
instance Convertible A B
instance Convertible B A
aToB :: A -> B
aToB = convert
bToA :: B -> A
bToA = convert
data List1 a = Nil1 | Cons1 a (List1 a) deriving Generic
data List2 a = Nil2 | Cons2 a (List2 a) deriving Generic
instance Convertible a b => Convertible (List1 a) (List2 b)
instance Convertible a b => Convertible (List2 a) (List1 b)
list1ToList2 :: List1 A -> List2 B
list1ToList2 = convert
list2ToList1 :: List2 A -> List1 B
list2ToList1 = convert
@jappeace
Copy link

jappeace commented Feb 3, 2022

I personally think this is a bad idea for the following reasons:

  1. names no longer matter, you've no idea what converts to what because everything just "converts", only types tell you something, but not everything, because there are often many isomorphisms. Type classes aren't well suited for this, use lens or plain functions.
  2. constructor order suddenly matters, but there is no way to assert this.
  3. casuall abuse, which is so easy to fall for will lead to beauties like:
    main :: IO ()
    main = convert $ convert $ convert ()
    
    you tell me what that does.

In my opinion you're much better of to use ordinary functions and pattern matching.

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