packages feed

purescript-0.10.0: examples/passing/GHCGenerics.purs

-- An example to show how we could implement GHC-style Generics using
-- functional dependencies.
--
-- See https://hackage.haskell.org/package/base-4.9.0.0/docs/GHC-Generics.html

module Main where

import Prelude
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log, logShow)

-- Representation for types with no constructors
data V1

-- Representation for constructors with no arguments
data U1 = U1

-- Representation for sum types
data Sum a b = Inl a | Inr b

infixr 5 type Sum as +

-- Representation for product types
data Product a b = Product a b

infixr 6 type Product as *

-- Representation for data constructors, with the data constructor name indicated
-- at the type level.
data Ctor (name :: Symbol) a = Ctor a

-- Representation for occurrences of other types in a data type definition.
data K a = K a

-- The Generic class asserts the existence of a type function from "real" types
-- to representation types, and an isomorphism between them.
class Generic a repr | a -> repr where
  to :: a -> repr
  from :: repr -> a

-- We can write an instance for the (recursive) type of lists. Note that these
-- instances would be generated by the compiler ideally.
data List a = Nil | Cons a (List a)

instance genericList :: Generic (List a) (Ctor "Nil" U1 + Ctor "Cons" (K a * K (List a))) where
  to Nil = Inl (Ctor U1)
  to (Cons x xs) = Inr (Ctor (Product (K x) (K xs)))
  from (Inl (Ctor U1)) = Nil
  from (Inr (Ctor (Product (K x) (K xs)))) = Cons x xs

-- We'd like to refect type level strings (for data constructor names) at the value
-- level, so that we can "show" them. Again, these instances would ideally be derived
-- for us.
class KnownSymbol (sym :: Symbol) where
  symbol :: forall proxy. proxy sym -> String

instance knownSymbolNil :: KnownSymbol "Nil" where
  symbol _ = "Nil"

instance knownSymbolCons :: KnownSymbol "Cons" where
  symbol _ = "Cons"

-- A proxy for a type-level string.
data SProxy (sym :: Symbol) = SProxy

-- To write generic functions, we create a corresponding type class, and use the
-- type class machinery to infer the correct function based on the representation
-- type.
class GShow a where
  gShow :: a -> String

-- Now provide instances for GShow for the appropriate representation types.
-- Note: we don't have to implement all instances here.

instance gShowU1 :: GShow U1 where
  gShow _ = ""

instance gShowSum :: (GShow a, GShow b) => GShow (a + b) where
  gShow (Inl a) = gShow a
  gShow (Inr b) = gShow b

instance gShowProduct :: (GShow a, GShow b) => GShow (a * b) where
  gShow (Product a b) = gShow a <> gShow b

instance gShowCtor :: (KnownSymbol ctor, GShow a) => GShow (Ctor ctor a) where
  gShow (Ctor a) = "(" <> symbol (SProxy :: SProxy ctor) <> gShow a <> ")"

instance gShowK :: Show a => GShow (K a) where
  gShow (K a) = " " <> show a

-- Now we can implement a generic show function which uses the GShow instance
-- on the representation type.
genericShow :: forall a repr. (Generic a repr, GShow repr) => a -> String
genericShow x = gShow (to x)

-- Note how the required instance here is Show a, and not Generic a.
-- This allows us to use generic programming on a wider variety of types
-- (including types which contain foreign types) than we can use now.
instance showList :: Show a => Show (List a) where
  show xs = genericShow xs -- (we need to eta expand here to avoid stack overflow
                           -- due to recursion implicit in the instance lookup)

-- Another example: Eq

class GEq a where
  gEq :: a -> a -> Boolean

instance gEqU1 :: GEq U1 where
  gEq _ _ = true

instance gEqSum :: (GEq a, GEq b) => GEq (a + b) where
  gEq (Inl a1) (Inl a2) = gEq a1 a2
  gEq (Inr b1) (Inr b2) = gEq b1 b2
  gEq _ _ = false

instance gEqProduct :: (GEq a, GEq b) => GEq (a * b) where
  gEq (Product a1 b1) (Product a2 b2) = gEq a1 a2 && gEq b1 b2

instance gEqCtor :: (KnownSymbol ctor, GEq a) => GEq (Ctor ctor a) where
  gEq (Ctor a1) (Ctor a2) = gEq a1 a2

instance gEqK :: Eq a => GEq (K a) where
  gEq (K a1) (K a2) = a1 == a2

genericEq :: forall a repr. (Generic a repr, GEq repr) => a -> a -> Boolean
genericEq x y = gEq (to x) (to y)

instance eqList :: Eq a => Eq (List a) where
  eq xs ys = genericEq xs ys

main :: Eff (console :: CONSOLE) Unit
main = do
  logShow (Cons 1 Nil)
  logShow (Cons 1 (Cons 2 Nil))
  logShow (Cons 'x' (Cons 'y' (Cons 'z' Nil)))

  logShow (Cons 1 (Cons 2 Nil) == Cons 1 (Cons 2 Nil))
  logShow (Cons 1 (Cons 2 Nil) == Cons 1 Nil)

  log "Done"