-- TODO: move this file somewhere else, like "Language.Hakaru.IClasses"
{-# LANGUAGE CPP
           , PolyKinds
           , DataKinds
           , TypeOperators
           , GADTs
           , TypeFamilies
           , Rank2Types
           , ScopedTypeVariables
           #-}
{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
----------------------------------------------------------------
--                                                    2016.04.28
-- |
-- Module      :  Language.Hakaru.Syntax.IClasses
-- Copyright   :  Copyright (c) 2016 the Hakaru team
-- License     :  BSD3
-- Maintainer  :  wren@community.haskell.org
-- Stability   :  experimental
-- Portability :  GHC-only
--
-- A collection of classes generalizing standard classes in order
-- to support indexed types.
--
-- TODO: DeriveDataTypeable for all our newtypes?
----------------------------------------------------------------
module Language.Hakaru.Syntax.IClasses
    (
    -- * Showing indexed types
      Show1(..), shows1, showList1
    , Show2(..), shows2, showList2
    -- ** Some more-generic helper functions for showing things
    , showListWith
    , showTuple
    -- ** some helpers for implementing the instances
    , showParen_0
    , showParen_1
    , showParen_2
    , showParen_01
    , showParen_02
    , showParen_11
    , showParen_12
    , showParen_22
    , showParen_010
    , showParen_011
    , showParen_111

    -- * Equality
    , Eq1(..)
    , Eq2(..)
    , TypeEq(..), symmetry, transitivity, congruence
    , JmEq1(..)
    , JmEq2(..)

    -- * Generalized abstract nonsense
    , Functor11(..), Fix11(..), cata11, ana11, hylo11
    , Functor12(..)
    , Functor21(..)
    , Functor22(..)
    , Functor31(..)
    , Foldable11(..), Lift1(..)
    , Foldable21(..), Lift2(..)
    , Foldable22(..)
    , Foldable31(..)
    , Traversable11(..)
    , Traversable21(..)
    , Traversable31(..)
    , Traversable22(..)

    -- * Helper types
    , Some1(..)
    , Some2(..)
    , Pair1(..), fst1, snd1
    , Pair2(..), fst2, snd2
    -- ** List types
    , type (++), eqAppendIdentity, eqAppendAssoc
    , List1(..), append1
    , DList1(..), toList1, fromList1, dnil1, dcons1, dsnoc1, dsingleton1, dappend1
    ) where

import Prelude hiding   (id, (.))
import Control.Category (Category(..))
import Unsafe.Coerce    (unsafeCoerce)
#if __GLASGOW_HASKELL__ < 710
import Data.Monoid      (Monoid(..))
import Control.Applicative
#endif

----------------------------------------------------------------
----------------------------------------------------------------
-- TODO: cf., <http://hackage.haskell.org/package/abt-0.1.1.0>
-- | Uniform variant of 'Show' for @k@-indexed types. This differs
-- from @transformers:@'Data.Functor.Classes.Show1' in being
-- polykinded, like it ought to be.
--
-- Alas, I don't think there's any way to derive instances the way
-- we can derive for 'Show'.
class Show1 (a :: k -> *) where
    {-# MINIMAL showsPrec1 | show1 #-}

    showsPrec1 :: Int -> a i -> ShowS
    showsPrec1 _ x s = show1 x ++ s

    show1 :: a i -> String
    show1 x = shows1 x ""

shows1 :: (Show1 a) => a i -> ShowS
shows1 =  showsPrec1 0

showList1 :: (Show1 a) => [a i] -> ShowS
showList1 = showListWith shows1

{-
-- BUG: these require (Show (i::k)) in the class definition of Show1
instance Show1 Maybe where
    showsPrec1 = showsPrec
    show1      = show

instance Show1 [] where
    showsPrec1 = showsPrec
    show1      = show

instance Show1 ((,) a) where
    showsPrec1 = showsPrec
    show1      = show

instance Show1 (Either a) where
    showsPrec1 = showsPrec
    show1      = show
-}


----------------------------------------------------------------
-- TODO: how to show, in general, that @Show2 a@ implies @Show1 (a i)@ for all @i@... This is needed for 'Datum' which uses the 'LC_' trick...
class Show2 (a :: k1 -> k2 -> *) where
    {-# MINIMAL showsPrec2 | show2 #-}

    showsPrec2 :: Int -> a i j -> ShowS
    showsPrec2 _ x s = show2 x ++ s

    show2 :: a i j -> String
    show2 x = shows2 x ""

shows2 :: (Show2 a) => a i j -> ShowS
shows2 =  showsPrec2 0

showList2 :: Show2 a => [a i j] -> ShowS
showList2 = showListWith shows2


----------------------------------------------------------------
-- This implementation taken from 'showList' in base-4.8:"GHC.Show",
-- generalizing over the showing function. Looks like this is available from base-4.8.2.0:"Text.Show"
showListWith :: (a -> ShowS) -> [a] -> ShowS
showListWith f = start
    where
    start []     s = "[]" ++ s
    start (x:xs) s = '[' : f x (go xs)
        where
        go []     = ']' : s
        go (y:ys) = ',' : f y (go ys)


-- This implementation taken from 'show_tuple' in base-4.8:"GHC.Show",
-- verbatim.
showTuple :: [ShowS] -> ShowS
showTuple ss
    = showChar '('
    . foldr1 (\s r -> s . showChar ',' . r) ss
    . showChar ')'


showParen_0 :: Show a => Int -> String -> a -> ShowS
showParen_0 p s e =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec 11 e
        )

showParen_1 :: Show1 a => Int -> String -> a i -> ShowS
showParen_1 p s e =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec1 11 e
        )

showParen_2 :: Show2 a => Int -> String -> a i j -> ShowS
showParen_2 p s e =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec2 11 e
        )

showParen_01 :: (Show b, Show1 a) => Int -> String -> b -> a i -> ShowS
showParen_01 p s e1 e2 =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec  11 e1
        . showString " "
        . showsPrec1 11 e2
        )

showParen_02 :: (Show b, Show2 a) => Int -> String -> b -> a i j -> ShowS
showParen_02 p s e1 e2 =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec  11 e1
        . showString " "
        . showsPrec2 11 e2
        )

showParen_11 :: (Show1 a, Show1 b) => Int -> String -> a i -> b j -> ShowS
showParen_11 p s e1 e2 =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec1 11 e1
        . showString " "
        . showsPrec1 11 e2
        )

showParen_12 :: (Show1 a, Show2 b) => Int -> String -> a i -> b j l -> ShowS
showParen_12 p s e1 e2 =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec1 11 e1
        . showString " "
        . showsPrec2 11 e2
        )

showParen_22 :: (Show2 a, Show2 b) => Int -> String -> a i1 j1 -> b i2 j2 -> ShowS
showParen_22 p s e1 e2 =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec2 11 e1
        . showString " "
        . showsPrec2 11 e2
        )

showParen_010
    :: (Show a, Show1 b, Show c)
    => Int -> String -> a -> b i -> c -> ShowS
showParen_010 p s e1 e2 e3 =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec  11 e1
        . showString " "
        . showsPrec1 11 e2
        . showString " "
        . showsPrec  11 e3
        )

showParen_011
    :: (Show a, Show1 b, Show1 c)
    => Int -> String -> a -> b i -> c j -> ShowS
showParen_011 p s e1 e2 e3 =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec  11 e1
        . showString " "
        . showsPrec1 11 e2
        . showString " "
        . showsPrec1 11 e3
        )

showParen_111
    :: (Show1 a, Show1 b, Show1 c)
    => Int -> String -> a i -> b j -> c k -> ShowS
showParen_111 p s e1 e2 e3 =
    showParen (p > 9)
        ( showString s
        . showString " "
        . showsPrec1 11 e1
        . showString " "
        . showsPrec1 11 e2
        . showString " "
        . showsPrec1 11 e3
        )

----------------------------------------------------------------
----------------------------------------------------------------
-- | Uniform variant of 'Eq' for homogeneous @k@-indexed types.
-- N.B., we keep this separate from the 'JmEq1' class because for
-- some types we may be able to decide 'eq1' while not being able
-- to decide 'jmEq1' (e.g., if using phantom types rather than
-- GADTs). N.B., this function returns value\/term equality! That
-- is, the following four laws must hold relating the 'Eq1' class
-- to the 'Eq' class:
--
--     (1) if @eq1 x y == True@, then @x@ and @y@ have the same
--         type index and @(x == y) == True@
--     (2) if @eq1 x y == False@ where @x@ and @y@ have the same
--         type index, then @(x == y) == False@
--     (3) if @(x == y) == True@, then @eq1 x y == True@
--     (4) if @(x == y) == False@, then @eq1 x y == False@
--
-- Alas, I don't think there's any way to derive instances the way
-- we can derive for 'Eq'.
class Eq1 (a :: k -> *) where
    eq1 :: a i -> a i -> Bool
    -- TODO: how do we give the default instance for whenever we have a JmEq1 instance?
    -- TODO: is there a way to require the induced @Eq (a i)@ instance to be given?


class Eq2 (a :: k1 -> k2 -> *) where
    eq2 :: a i j -> a i j -> Bool



----------------------------------------------------------------
----------------------------------------------------------------
-- TODO: it'd be okay to use @(:~:)@ from "Data.Typeable" instead of defining our own. Except that one doesn't define the Category instance, symmetry, or congruence...

-- | Concrete proofs of type equality. In order to make use of a
-- proof @p :: TypeEq a b@, you must pattern-match on the 'Refl'
-- constructor in order to show GHC that the types @a@ and @b@ are
-- equal.
data TypeEq :: k -> k -> * where
    Refl :: TypeEq a a

instance Category TypeEq where
    id          = Refl
    Refl . Refl = Refl

-- | Type equality is symmetric.
symmetry :: TypeEq a b -> TypeEq b a
symmetry Refl = Refl

-- | Type equality is transitive. N.B., this is has a more general
-- type than @(.)@
transitivity :: TypeEq a b -> TypeEq b c -> TypeEq a c
transitivity Refl Refl = Refl

-- | Type constructors are extensional.
congruence :: TypeEq a b -> TypeEq (f a) (f b)
congruence Refl = Refl


-- TODO: Should we add an additional method which only checks for index equality, ignoring possible differences at the term\/value level?
--
-- | Uniform variant of 'Eq' for heterogeneous @k@-indexed types.
-- N.B., this function returns value\/term equality! That is, the
-- following four laws must hold relating the 'JmEq1' class to the
-- 'Eq1' class:
--
--     (1) if @jmEq1 x y == Just Refl@, then @x@ and @y@ have the
--         same type index and @eq1 x y == True@
--     (2) if @jmEq1 x y == Nothing@ where @x@ and @y@ have the
--         same type index, then @eq1 x y == False@
--     (3) if @eq1 x y == True@, then @jmEq1 x y == Just Refl@
--     (4) if @eq1 x y == False@, then @jmEq1 x y == Nothing@
--
-- Alas, I don't think there's any way to derive instances the way
-- we can derive for 'Eq'.
class Eq1 a => JmEq1 (a :: k -> *) where
    jmEq1 :: a i -> a j -> Maybe (TypeEq i j)

class Eq2 a => JmEq2 (a :: k1 -> k2 -> *) where
    jmEq2 :: a i1 j1 -> a i2 j2 -> Maybe (TypeEq i1 i2, TypeEq j1 j2)


----------------------------------------------------------------
----------------------------------------------------------------
-- TODO: rather than having this plethora of classes for different indexing, define newtypes for 1-natural transformations, 2-natural transformations, etc; and then define a single higher-order functor class which is parameterized by the input and output categories.

-- | A functor on the category of @k@-indexed types (i.e., from
-- @k@-indexed types to @k@-indexed types). We unify the two indices,
-- because that seems the most helpful for what we're doing; we
-- could, of course, offer a different variant that maps @k1@-indexed
-- types to @k2@-indexed types...
--
-- Alas, I don't think there's any way to derive instances the way
-- we can derive for 'Functor'.
class Functor11 (f :: (k1 -> *) -> k2 -> *) where
    fmap11 :: (forall i. a i -> b i) -> f a j -> f b j

class Functor12 (f :: (k1 -> *) -> k2 -> k3 -> *) where
    fmap12 :: (forall i. a i -> b i) -> f a j l -> f b j l

-- | A functor from @(k1,k2)@-indexed types to @k3@-indexed types.
class Functor21 (f :: (k1 -> k2 -> *) -> k3 -> *) where
    fmap21 :: (forall h i. a h i -> b h i) -> f a j -> f b j


-- | A functor from @(k1,k2)@-indexed types to @(k3,k4)@-indexed types.
class Functor22 (f :: (k1 -> k2 -> *) -> k3 -> k4 -> *) where
    fmap22 :: (forall h i. a h i -> b h i) -> f a j l -> f b j l


----------------------------------------------------------------
newtype Fix11 (f :: (k -> *) -> k -> *) (i :: k) =
    Fix11 { unFix11 :: f (Fix11 f) i }

cata11
    :: forall f a j
    .  (Functor11 f)
    => (forall i. f a i -> a i)
    -> Fix11 f j -> a j
cata11 alg = go
    where
    go :: forall j'. Fix11 f j' -> a j'
    go = alg . fmap11 go . unFix11

ana11
    :: forall f a j
    .  (Functor11 f)
    => (forall i. a i -> f a i)
    -> a j -> Fix11 f j
ana11 coalg = go
    where
    go :: forall j'. a j' -> Fix11 f j'
    go = Fix11 . fmap11 go . coalg

hylo11
    :: forall f a b j
    .  (Functor11 f)
    => (forall i. a i -> f a i)
    -> (forall i. f b i -> b i)
    -> a j
    -> b j
hylo11 coalg alg = go
    where
    go :: forall j'. a j' -> b j'
    go = alg . fmap11 go . coalg

-- TODO: a tracing evaluator: <http://www.timphilipwilliams.com/posts/2013-01-16-fixing-gadts.html>


----------------------------------------------------------------
-- TODO: in theory we could define some Monoid1 class to avoid the
-- explicit dependency on Lift1 in fold1's type. But we'd need that
-- Monoid1 class to have some sort of notion of combining things
-- at different indices...

-- | A foldable functor on the category of @k@-indexed types.
--
-- Alas, I don't think there's any way to derive instances the way
-- we can derive for 'Foldable'.
class Functor11 f => Foldable11 (f :: (k1 -> *) -> k2 -> *) where
    {-# MINIMAL fold11 | foldMap11 #-}

    fold11 :: (Monoid m) => f (Lift1 m) i -> m
    fold11 = foldMap11 unLift1

    foldMap11 :: (Monoid m) => (forall i. a i -> m) -> f a j -> m
    foldMap11 f = fold11 . fmap11 (Lift1 . f)

-- TODO: standard Foldable wrappers 'and11', 'or11', 'all11', 'any11',...


class Functor21 f => Foldable21 (f :: (k1 -> k2 -> *) -> k3 -> *) where
    {-# MINIMAL fold21 | foldMap21 #-}

    fold21 :: (Monoid m) => f (Lift2 m) j -> m
    fold21 = foldMap21 unLift2

    foldMap21 :: (Monoid m) => (forall h i. a h i -> m) -> f a j -> m
    foldMap21 f = fold21 . fmap21 (Lift2 . f)


class Functor22 f =>
    Foldable22 (f :: (k1 -> k2 -> *) -> k3 -> k4 -> *)
    where
    {-# MINIMAL fold22 | foldMap22 #-}

    fold22 :: (Monoid m) => f (Lift2 m) j l -> m
    fold22 = foldMap22 unLift2

    foldMap22 :: (Monoid m) => (forall h i. a h i -> m) -> f a j l -> m
    foldMap22 f = fold22 . fmap22 (Lift2 . f)

class Functor31 (f :: (k1 -> k2 -> *) -> k3 -> k4 -> *) where
    fmap31 :: (forall h i. a h i -> b h i) -> f a j k -> f b j k

class Functor31 f => Foldable31 (f :: (k1 -> k2 -> *) -> k3 -> k4 -> *) where
    foldMap31 :: (Monoid m) => (forall h i. a h i -> m) -> f a j k -> m

----------------------------------------------------------------
----------------------------------------------------------------
class Foldable11 t => Traversable11 (t :: (k1 -> *) -> k2 -> *) where
    traverse11
        :: Applicative f
        => (forall i. a i -> f (b i))
        -> t a j
        -> f (t b j)
    {-
    sequenceA :: Applicative f => t (f a) -> f (t a)
    mapM :: Monad m => (a -> m b) -> t a -> m (t b)
    sequence :: Monad m => t (m a) -> m (t a)
    -}


class Foldable21 t => Traversable21 (t :: (k1 -> k2 -> *) -> k3 -> *) where
    traverse21
        :: Applicative f
        => (forall h i. a h i -> f (b h i))
        -> t a j
        -> f (t b j)

class Foldable22 t =>
    Traversable22 (t :: (k1 -> k2 -> *) -> k3 -> k4 -> *)
    where
    traverse22
        :: Applicative f
        => (forall h i. a h i -> f (b h i))
        -> t a j l
        -> f (t b j l)

class Foldable31 t => Traversable31 (t :: (k1 -> k2 -> *) -> k3 -> k4 -> *) where
    traverse31
        :: Applicative f
        => (forall h i. a h i -> f (b h i))
        -> t a j k
        -> f (t b j k)

----------------------------------------------------------------
----------------------------------------------------------------
-- | Any unindexed type can be lifted to be (trivially) @k@-indexed.
newtype Lift1 (a :: *) (i :: k) =
    Lift1 { unLift1 :: a }
    deriving (Read, Show, Eq, Ord)

instance Show a => Show1 (Lift1 a) where
    showsPrec1 p (Lift1 x) = showsPrec p x
    show1        (Lift1 x) = show x

instance Eq a => Eq1 (Lift1 a) where
    eq1 (Lift1 a) (Lift1 b) = a == b


----------------------------------------------------------------
-- | Any unindexed type can be lifted to be (trivially) @(k1,k2)@-indexed.
newtype Lift2 (a :: *) (i :: k1) (j :: k2) =
    Lift2 { unLift2 :: a }
    deriving (Read, Show, Eq, Ord)

instance Show a => Show2 (Lift2 a) where
    showsPrec2 p (Lift2 x) = showsPrec p x
    show2        (Lift2 x) = show x

instance Show a => Show1 (Lift2 a i) where
    showsPrec1 p (Lift2 x) = showsPrec p x
    show1        (Lift2 x) = show x

instance Eq a => Eq2 (Lift2 a) where
    eq2 (Lift2 a) (Lift2 b) = a == b

instance Eq a => Eq1 (Lift2 a i) where
    eq1 (Lift2 a) (Lift2 b) = a == b


----------------------------------------------------------------
-- BUG: haddock doesn't like annotations on GADT constructors. So
-- here we'll avoid using the GADT syntax, even though it'd make
-- the data type declaration prettier\/cleaner.
-- <https://github.com/hakaru-dev/hakaru/issues/6>
--
-- | Existentially quantify over a single index.
-- TODO: replace 'SomeVariable' with @(Some1 Variable)@
data Some1 (a :: k -> *)
    = forall i. Some1 !(a i)

instance Show1 a => Show (Some1 a) where
    showsPrec p (Some1 x) = showParen_1 p "Some1" x

instance JmEq1 a  => Eq (Some1 a) where
    Some1 x == Some1 y = maybe False (const True) (jmEq1 x y)


-- | Existentially quantify over two indices.
data Some2 (a :: k1 -> k2 -> *)
    = forall i j. Some2 !(a i j)

instance Show2 a => Show (Some2 a) where
    showsPrec p (Some2 x) = showParen_2 p "Some2" x

instance JmEq2 a  => Eq (Some2 a) where
    Some2 x == Some2 y = maybe False (const True) (jmEq2 x y)


----------------------------------------------------------------
-- | A lazy pairing of identically @k@-indexed values.
data Pair1 (a :: k -> *) (b :: k -> *) (i :: k) =
    Pair1 (a i) (b i)

fst1 :: Pair1 a b i -> a i
fst1 (Pair1 x _) = x

snd1 :: Pair1 a b i -> b i
snd1 (Pair1 _ y) = y

instance (Show1 a, Show1 b) => Show1 (Pair1 a b) where
    showsPrec1 p (Pair1 x y) = showParen_11 p "Pair1" x y

instance (Show1 a, Show1 b) => Show (Pair1 a b i) where
    showsPrec = showsPrec1
    show      = show1

-- TODO: derived Eq, JmEq, functor, foldable, traversable instances

----------------------------------------------------------------
-- | A lazy pairing of identically @(k1,k2)@-indexed values.
data Pair2 (a :: k1 -> k2 -> *) (b :: k1 -> k2 -> *) (i :: k1) (j :: k2) =
    Pair2 (a i j) (b i j)

fst2 :: Pair2 a b i j -> a i j
fst2 (Pair2 x _) = x

snd2 :: Pair2 a b i j -> b i j
snd2 (Pair2 _ y) = y

instance (Show2 a, Show2 b) => Show2 (Pair2 a b) where
    showsPrec2 p (Pair2 x y) = showParen_22 p "Pair2" x y

instance (Show2 a, Show2 b) => Show1 (Pair2 a b i) where
    showsPrec1 = showsPrec2
    show1      = show2

instance (Show2 a, Show2 b) => Show (Pair2 a b i j) where
    showsPrec = showsPrec1
    show      = show1

-- TODO: derived Eq, JmEq, functor, foldable, traversable instances


----------------------------------------------------------------
----------------------------------------------------------------
-- TODO: move all the list stuff off somewhere else

-- BUG: how do we actually use the term-level @(++)@ at the type level? Or do we have to redefine it ourselves (as below)? If we define it ourselves, how can we make sure it's usable? In particular, how can we prove associativity and that @'[]@ is a /two-sided/ identity element?
type family (xs :: [k]) ++ (ys :: [k]) :: [k] where
    '[]       ++ ys = ys
    (x ': xs) ++ ys = x ': (xs ++ ys)

{-
-- BUG: having the instances for @[[HakaruFun]]@ and @[HakaruFun]@ precludes giving a general kind-polymorphic data instance for type-level lists; so we have to monomorphize it to just the @[Hakaru]@ kind.
-- TODO: we should figure out some way to clean that up without introducing too much ambiguity\/overloading of the constructor names.
data instance Sing (xs :: [Hakaru]) where
    SNil  :: Sing ('[] :: [Hakaru])
    SCons :: !(Sing x) -> !(Sing xs) -> Sing ((x ': xs) :: [Hakaru])

-- BUG: ghc calls all these orphan instances, even though the data instance is defined here... Will that actually cause problems? Should we move this to TypeEq.hs?
instance Show1 (Sing :: [Hakaru] -> *) where
    showsPrec1 p s =
        case s of
        SNil        -> showString     "SNil"
        SCons s1 s2 -> showParen_11 p "SCons" s1 s2
instance Show (Sing (xs :: [Hakaru])) where
    showsPrec = showsPrec1
    show      = show1
instance SingI ('[] :: [Hakaru]) where
    sing = SNil
instance (SingI x, SingI xs) => SingI ((x ': xs) :: [Hakaru]) where
    sing = SCons sing sing
-}


-- | The empty list is (also) a right-identity for @('++')@. Because
-- we define @('++')@ by induction on the first argument, this
-- identity doesn't come for free but rather must be proven.
eqAppendIdentity :: proxy xs -> TypeEq xs (xs ++ '[])
-- This version should be used for runtime performance
eqAppendIdentity _ = unsafeCoerce Refl
{-
-- This version demonstrates that our use of unsafeCoerce is sound
-- BUG: to have an argument of type @Sing xs@, instead of an arbitrary @proxy xs@, we'd need to store the singleton somewhere (prolly in the 'Branch', for the use site in TypeCheck.hs) or else produce it somehow
eqAppendIdentity :: Sing (xs :: [Hakaru]) -> TypeEq xs (xs ++ '[])
eqAppendIdentity SNil        = Refl
eqAppendIdentity (SCons _ s) = case eqAppendIdentity s of Refl -> Refl
-}


-- To make a version that doesn't require proxies, we'd need to
-- enable -XAllowAmbiguousTypes. We'd need to do this even just to
-- drop the second or third proxies, which aren't needed for the
-- safe version.
--
-- | @('++')@ is associative. This identity doesn't come for free
-- but rather must be proven.
eqAppendAssoc
    :: proxy1 xs
    -> proxy2 ys
    -> proxy3 zs
    -> TypeEq ((xs ++ ys) ++ zs) (xs ++ (ys ++ zs))
-- This version should be used for runtime performance
eqAppendAssoc _ _ _ = unsafeCoerce Refl
{-
-- This version demonstrates that our use of unsafeCoerce is sound
-- BUG: to have the arguments be of type @Sing xs@, instead of arbitrary proxy types, we'd need to store the singletons somewhere (for the use site in TypeCheck.hs), but where?
eqAppendAssoc
    :: Sing (xs :: [Hakaru])
    -> Sing (ys :: [Hakaru])
    -> Sing (zs :: [Hakaru])
    -> TypeEq ((xs ++ ys) ++ zs) (xs ++ (ys ++ zs))
eqAppendAssoc SNil         _  _  = Refl
eqAppendAssoc (SCons _ sx) sy sz =
    case eqAppendAssoc sx sy sz of Refl -> Refl
-}

-- TODO: eqAppendNil :: proxy xs -> proxy ys -> TypeEq '[] (xs ++ ys) -> (TypeEq '[] xs, TypeEq '[] ys)


----------------------------------------------------------------
infixr 5 `Cons1`

-- | A /lazy/ list of @k@-indexed elements, itself indexed by the
-- list of indices
data List1 :: (k -> *) -> [k] -> * where
    Nil1  :: List1 a '[]
    Cons1 :: a x -> List1 a xs -> List1 a (x ': xs)


append1 :: List1 a xs -> List1 a ys -> List1 a (xs ++ ys)
append1 Nil1         ys = ys
append1 (Cons1 x xs) ys = Cons1 x (append1 xs ys)


instance Show1 a => Show1 (List1 a) where
    showsPrec1 _ Nil1         = showString     "Nil1"
    showsPrec1 p (Cons1 x xs) = showParen_11 p "Cons1" x xs

instance Show1 a => Show (List1 a xs) where
    showsPrec = showsPrec1
    show      = show1

instance JmEq1 a  => JmEq1 (List1 a) where
    jmEq1 Nil1         Nil1         = Just Refl
    jmEq1 (Cons1 x xs) (Cons1 y ys) =
        jmEq1 x  y  >>= \Refl ->
        jmEq1 xs ys >>= \Refl ->
        Just Refl
    jmEq1 _ _ = Nothing

instance Eq1 a  => Eq1 (List1 a) where
    eq1 Nil1         Nil1         = True
    eq1 (Cons1 x xs) (Cons1 y ys) = eq1 x y && eq1 xs ys
    eq1 _            _            = False

instance Eq1 a  => Eq (List1 a xs) where
    (==) = eq1

instance Functor11 List1 where
    fmap11 _ Nil1         = Nil1
    fmap11 f (Cons1 x xs) = Cons1 (f x) (fmap11 f xs)

instance Foldable11 List1 where
    foldMap11 _ Nil1         = mempty
    foldMap11 f (Cons1 x xs) = f x `mappend` foldMap11 f xs

instance Traversable11 List1 where
    traverse11 _ Nil1         = pure Nil1
    traverse11 f (Cons1 x xs) = Cons1 <$> f x <*> traverse11 f xs


----------------------------------------------------------------
-- TODO: cf the interface of <https://hackage.haskell.org/package/dlist-0.7.1.2/docs/Data-DList.html>
-- | A difference-list variant of 'List1'.
newtype DList1 a xs =
    DList1 { unDList1 :: forall ys. List1 a ys -> List1 a (xs ++ ys) }


toList1 :: DList1 a xs -> List1 a xs
toList1 dx@(DList1 xs) =
    case eqAppendIdentity dx of
    Refl -> xs Nil1

fromList1 :: List1 a xs -> DList1 a xs
fromList1 xs = DList1 (append1 xs)
    -- N.B., using @DList1 . append1@ doesn't type check

dnil1 :: DList1 a '[]
dnil1 = DList1 id

dcons1 :: a x -> DList1 a xs -> DList1 a (x ': xs)
dcons1 x (DList1 xs) = DList1 (Cons1 x . xs)

-- TODO: for this access pattern it's prolly better to define some @DListR@ where the index is in the reverse order of the list itself (or else uses type-level snoc-lists); that way we can avoid needing to use 'eqAppendAssoc' at every step...
dsnoc1 :: DList1 a xs -> a x -> DList1 a (xs ++ '[ x ])
dsnoc1 dx@(DList1 xs) x =
    DList1 $ \ys ->
        case eqAppendAssoc dx (dsingleton1 x) ys of
        Refl -> xs (Cons1 x ys)

-- HACK: we need to give this a top-level definition rather than
-- inlining it in order to prove that the resulting index is @[x]@
-- rather than possibly some other @(x:xs)@. No, I'm not sure why
-- GHC can't infer that...
dsingleton1 :: a x -> DList1 a '[ x ]
dsingleton1 x = DList1 (Cons1 x)

dappend1 :: DList1 a xs -> DList1 a ys -> DList1 a (xs ++ ys)
dappend1 dx@(DList1 xs) dy@(DList1 ys) =
    DList1 $ \zs ->
        case eqAppendAssoc dx dy zs of
        Refl -> xs (ys zs)

{-
instance Show1 a => Show1 (DList1 a) where
    showsPrec1 p xs =

instance Show1 a => Show (DList1 a xs) where
    showsPrec = showsPrec1
    show      = show1

instance JmEq1 a => JmEq1 (DList1 a) where
    jmEq1 xs ys =

instance Eq1 a => Eq1 (DList1 a) where
    eq1 xs ys =

instance Eq1 a => Eq (DList1 a xs) where
    (==) = eq1

instance Functor11 DList1 where
    fmap11 f xs =

instance Foldable11 DList1 where
    foldMap11 f xs =

instance Traversable11 DList1 where
    traverse11 f xs =
-}

----------------------------------------------------------------
----------------------------------------------------------- fin.
