{-# LANGUAGE PolyKinds , MultiParamTypeClasses , FlexibleInstances
 , FlexibleContexts
 , ScopedTypeVariables
 , TypeOperators
 , UndecidableInstances  -- for (Shapely tab, Applied t tab) constraint
 #-}
module Data.Shapely.Spine (
    -- * Constructing a type's Spine
      SpineOf(..), (:-:), (:-!)
    -- * Working with Proxys
    , module Data.Proxy
    , proxyTypeOf
    ) where

import Data.Proxy
import Data.Shapely.Classes
import Data.Proxy.Kindness


-- | > proxyTypeOf = return
proxyTypeOf :: a -> Proxy a
proxyTypeOf = return

infixr :-:
type t :-: ts = (Proxy t, ts)
infixr :-!
type t1 :-! t2 = (Proxy t1, (Proxy t2, ()))

-- | The \"spine\" of some 'Shapely' instance type can be specified by
-- enumerating the types of its recursive subterms in a 'Product' of 'Proxy'
-- values. For instance the spine of @data L = Cons Int L | Empty@ would simply
-- be
--
-- > (Proxy :: Proxy L, ())
--
-- When parameterized types make up the recursive structure, like @[a]@ one can
-- specify the spine using just the base type (in this case @[]@), e.g.
--
-- > (Proxy :: Proxy [], ())
--
-- ...or the base type applied as far as desired and functions using this spine
-- will match potential product terms accordingly (e.g. 'coerceWith').
--
-- See "proxy-kindness" for utilities useful fo constructing 'Spine's, e.g.
-- 'unappliedOf'.
class SpineOf ts where
    -- | Allows for terse definition of the cluster of types that make up a
    -- type's spine. E.g.
    --
    -- > sp = spine :: (Foo ':-:' Bar ':-!' Baz)
    spine :: ts
instance (Shapely tab, Applied t tab, SpineOf ts)=> SpineOf (Proxy t, ts) where
    spine = (Proxy, spine :: ts)
instance SpineOf () where
    spine = ()