{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}


module Data.Cardinal
    ( Zero
    , Cardinal(..)

    , C0
    , C1
    , C2
    , C3
    , C4

    , Cardinality(..)
    , card

    , module Data.TypeAlgebra

    ) where

import Data.TypeAlgebra

data Zero

type C0 = Zero

type C1 = Succ C0

type C2 = Succ C1

type C3 = Succ C2

type C4 = Succ C3

-- | Cardinal number as a type. The associated data type @'Succ' a@
-- provides the next cardinal type. The method @'fromCardinal'@
-- provides a numeric representation of the cardinal number; it should
-- be independent on the argument and work on @'undefined'@.
class Cardinal a where
    data Succ a
    fromCardinal :: (Num i) => a -> i

instance Cardinal Zero where
    data Succ Zero
    fromCardinal _ = 0

instance Cardinal a => Cardinal (Succ a) where
    data Succ (Succ a)
    fromCardinal x = 1 + fromCardinal (p x)
        where p :: Succ a -> a
              p _ = undefined

instance Show Zero where
    show _ = "0"

instance Cardinal a => Show (Succ a) where
    show x = show (fromCardinal x :: Integer)

-- | The cardinality of a type is defined by its @'Cardinal'@ type
-- @'Card' a@.
class Cardinal (Card a) => Cardinality a where
    type Card a

-- | The numeric cardinality of a type. @'card'@ is independent on its
-- argument.
card :: (Cardinality a, Num i) => a -> i
card = fromCardinal . c
         where c :: a -> Card a
               c _ = undefined

instance Cardinal a => Sum a Zero where
    type a :+: Zero = a
    _ <+> _ = undefined

instance (Cardinal a, Cardinal b, Sum a b) => Sum a (Succ b) where
    type a :+: (Succ b) = Succ (a :+: b)
    _ <+> _ = undefined


instance Cardinal a => Prod a Zero where
    type a :*: Zero = Zero
    _ <*> _ = undefined

instance (Cardinal a, Prod a b) => Prod a (Succ b) where
    type a :*: (Succ b) = a :+: (a :*: b)
    _ <*> _ = undefined