{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP                 #-}
{-# LANGUAGE KindSignatures      #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications    #-}
{-# LANGUAGE TypeOperators       #-}

{-# OPTIONS_GHC -Wall #-}

----------------------------------------------------------------------
-- |
-- Module      :  ShapedTypes.Sized
-- Copyright   :  (c) 2016 Conal Elliott
-- License     :  BSD3
--
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
-- 
-- Statically sized functors
----------------------------------------------------------------------

-- TODO: Reconsider whether I can use 'length' from 'Foldable' rather than the
-- Sized type class. Can 'Foldable.length' operate efficiently on our data types
-- (without traversing)?

module ShapedTypes.Sized (Sized(..),genericSize,sizeAF) where

-- TODO: explicit exports

import GHC.Generics

class Sized (f :: * -> *) where
  size :: Int
  dummySized :: ()
  dummySized = ()

-- TODO: Switch from f () to f Void or Proxy

-- | Generic 'size'
genericSize :: forall f. Sized (Rep1 f) => Int
genericSize = size @(Rep1 f)
{-# INLINABLE genericSize #-}

-- | Default for 'size' on an applicative foldable.
-- Warning: runs in linear time (though possibly at compile time).
sizeAF :: forall f. (Applicative f, Foldable f) => Int
sizeAF = sum (pure 1 :: f Int)

{--------------------------------------------------------------------
    Generics
--------------------------------------------------------------------}

instance Sized U1 where
  size = 0
  {-# INLINABLE size #-}

instance Sized Par1 where
  size = 1
  {-# INLINABLE size #-}

instance Sized (K1 i c) where
  size = 0
  {-# INLINABLE size #-}

instance Sized f => Sized (M1 i c f) where
  size = size @f
  {-# INLINABLE size #-}

instance (Sized g, Sized f) => Sized (g :.: f) where
  size = size @g * size @f
  {-# INLINABLE size #-}

instance (Sized f, Sized g) => Sized (f :*: g) where
  size = size @f + size @g
  {-# INLINABLE size #-}
