{-# LANGUAGE CPP #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}

-- | A GADT HList implementation
--
-- There exist other implementations of HList on hackage, but none seem to
-- be reliably maintained.
module Data.HList.HList
  ( HList(..)
  , Append
  , hAppend
  , HInit(..)
) where



import Prelude hiding (reverse)

import Data.Kind (Type)
import Data.Monoid (Monoid, mappend, mempty)
import Data.Semigroup

import Data.Proxy



data HList :: [Type] -> Type where
  HNil :: HList '[]
  (:+:) :: x -> HList xs -> HList (x ': xs)
  -- TCons :: x -> HList xs -> HList (Cons x xs)
  -- TNull :: HList Null

infixr 5 :+:

instance Show (HList '[]) where
  show _ = "HNil"

instance (Show a, Show (HList b)) => Show (HList (a ': b)) where
  show (x :+: y) = "(" ++ show x ++ ":+:" ++ show y ++ ")"

instance Semigroup (HList '[]) where
  _ <> _ = HNil
instance (Semigroup x, Semigroup (HList xs))
      => Semigroup (HList (x ': xs))
  where
    (x1 :+: xs1) <> (x2 :+: xs2) = (x1 <> x2) :+: (xs1 <> xs2)

instance Monoid (HList '[]) where
  mempty = HNil
  mappend = (<>)
instance (Semigroup x, Monoid x, Semigroup (HList xs), Monoid (HList xs))
      => Monoid (HList (x ': xs))
  where
    mempty = mempty :+: mempty
    mappend = (<>)

instance Eq (HList '[]) where
  HNil == HNil = True
  HNil /= HNil = False

instance (Eq x, Eq (HList xs))
      => Eq (HList (x ': xs))
  where
    x1 :+: xr1 == x2 :+: xr2 = x1==x2 && xr1==xr2
    x1 :+: xr1 /= x2 :+: xr2 = x1/=x2 || xr1/=xr2

-- cannot use the closed variant because of ghc-7.8.4.
-- (was not investigated more closely; there simply
--  is some syntax error for code which works fine with ghc-7.10.)
type family Append (l1::[Type]) (l2::[Type]) :: [Type]
type instance Append '[] l2 = l2
type instance Append (car1 ': cdr2) l2 = car1 ': Append cdr2 l2

hAppend :: HList ts1 -> HList ts2 -> HList (Append ts1 ts2)
hAppend HNil l = l
hAppend (x:+:xs) l = x :+: hAppend xs l

class HInit (l1 :: [Type]) where
  hInit :: forall l2 . Proxy l2 -> HList (Append l1 l2) -> HList l1
  hSplit :: forall l2 . HList (Append l1 l2) -> (HList l1, HList l2)

instance HInit '[] where
  hInit _ _ = HNil
  hSplit l = (HNil, l)
instance HInit l1 => HInit (x ': l1) where
  hInit p (x :+: xs)  = x :+: hInit p xs
#if !MIN_VERSION_base(4,9,0)
  hInit _ _           = error "cannot happen" -- see ghc trac #3927
#endif
  hSplit (x :+: xs) = let (l1, l2) = hSplit xs
                       in (x :+: l1, l2)
#if !MIN_VERSION_base(4,9,0)
  hSplit _          = error "cannot happen"
#endif
