{-# LANGUAGE CPP                        #-}
{-# LANGUAGE DeriveTraversable          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE PatternSynonyms            #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE UndecidableInstances       #-} -- XXX
{-# LANGUAGE ViewPatterns               #-}

-- |
-- Module      : Streamly.Internal.Data.List
-- Copyright   : (c) 2018 Composewell Technologies
--
-- License     : BSD3
-- Maintainer  : streamly@composewell.com
-- Stability   : experimental
-- Portability : GHC
--
-- Lists are just a special case of monadic streams. The stream type @SerialT
-- Identity a@ can be used as a replacement for @[a]@.  The 'List' type in this
-- module is just a newtype wrapper around @SerialT Identity@ for better type
-- inference when using the 'OverloadedLists' GHC extension. @List a@ provides
-- better performance compared to @[a]@. Standard list, string and list
-- comprehension syntax can be used with the 'List' type by enabling
-- 'OverloadedLists', 'OverloadedStrings' and 'MonadComprehensions' GHC
-- extensions.  There would be a slight difference in the 'Show' and 'Read'
-- strings of streamly list as compared to regular lists.
--
-- Conversion to stream types is free, any stream combinator can be used on
-- lists by converting them to streams.  However, for convenience, this module
-- provides combinators that work directly on the 'List' type.
--
--
-- @
-- List $ S.map (+ 1) $ toSerial (1 \`Cons\` Nil)
-- @
--
-- To convert a 'List' to regular lists, you can use any of the following:
--
-- * @toList . toSerial@ and @toSerial . fromList@
-- * 'Data.Foldable.toList' from "Data.Foldable"
-- * 'GHC.Exts.toList' and 'GHC.Exts.fromList' from 'IsList' in "GHC.Exts"
--
-- If you have made use of 'Nil' and 'Cons' constructors in the code and you
-- want to replace streamly lists with standard lists, all you need to do is
-- import these definitions:
--
-- @
-- type List = []
-- pattern Nil <- [] where Nil = []
-- pattern Cons x xs = x : xs
-- infixr 5 `Cons`
-- {-\# COMPLETE Cons, Nil #-}
-- @
--
-- See <src/docs/streamly-vs-lists.md> for more details and
-- <src/test/PureStreams.hs> for comprehensive usage examples.
--
module Streamly.Internal.Data.List
    (
#if __GLASGOW_HASKELL__ >= 800
    List (.., Nil, Cons)
#else
    List (..)
    , pattern Nil
    , pattern Cons
#endif
    -- XXX we may want to use rebindable syntax for variants instead of using
    -- different types (applicative do and apWith).
    , ZipList (..)
    , fromZipList
    , toZipList
    )
where

import Control.Arrow (second)
import Control.DeepSeq (NFData(..))
#if MIN_VERSION_deepseq(1,4,3)
import Control.DeepSeq (NFData1(..))
#endif
import Data.Functor.Identity (Identity, runIdentity)
#if __GLASGOW_HASKELL__ < 808
import Data.Semigroup (Semigroup(..))
#endif
import GHC.Exts (IsList(..), IsString(..))

import Streamly.Internal.Data.Stream.Serial (SerialT)
import Streamly.Internal.Data.Stream.Zip (ZipSerialM)

import qualified Streamly.Internal.Data.Stream.Prelude as P
import qualified Streamly.Internal.Data.Stream.StreamK as K

-- We implement list as a newtype instead of a type synonym to make type
-- inference easier when using -XOverloadedLists and -XOverloadedStrings. When
-- using a stream type the programmer needs to specify the Monad otherwise the
-- type remains ambiguous.
--
-- XXX once we separate consM from IsStream or remove the MonadIO and
-- MonadBaseControlIO dependency from it, then we can make this an instance of
-- IsStream and use the regular polymorphic functions on Lists as well. Once
-- that happens we can change the Show and Read instances as well to use "1 >:
-- 2 >: nil" etc. or should we use a separate constructor indicating the "List"
-- type ":>" for better inference?
--
-- | @List a@ is a replacement for @[a]@.
--
-- @since 0.6.0
newtype List a = List { toSerial :: SerialT Identity a }
    deriving (Show, Read, Eq, Ord, NFData
#if MIN_VERSION_deepseq(1,4,3)
    , NFData1
#endif
             , Semigroup, Monoid, Functor, Foldable
             , Applicative, Traversable, Monad)

instance (a ~ Char) => IsString (List a) where
    {-# INLINE fromString #-}
    fromString = List . P.fromList

-- GHC versions 8.0 and below cannot derive IsList
instance IsList (List a) where
    type (Item (List a)) = a
    {-# INLINE fromList #-}
    fromList = List . P.fromList
    {-# INLINE toList #-}
    toList = runIdentity . P.toList . toSerial

------------------------------------------------------------------------------
-- Patterns
------------------------------------------------------------------------------

-- Note: When using the OverloadedLists extension we should be able to pattern
-- match using the regular list contructors. OverloadedLists uses 'toList' to
-- perform the pattern match, it should not be too bad as it works lazily in
-- the Identity monad. We need these patterns only when not using that
-- extension.
--
-- | An empty list constructor and pattern that matches an empty 'List'.
-- Corresponds to '[]' for Haskell lists.
--
-- @since 0.6.0
pattern Nil :: List a
pattern Nil <- (runIdentity . K.null . toSerial -> True) where
    Nil = List K.nil

infixr 5 `Cons`

-- | A list constructor and pattern that deconstructs a 'List' into its head
-- and tail. Corresponds to ':' for Haskell lists.
--
-- @since 0.6.0
pattern Cons :: a -> List a -> List a
pattern Cons x xs <-
    (fmap (second List) . runIdentity . K.uncons . toSerial
        -> Just (x, xs)) where
            Cons x xs = List $ K.cons x (toSerial xs)

#if __GLASGOW_HASKELL__ >= 802
{-# COMPLETE Nil, Cons #-}
#endif

------------------------------------------------------------------------------
-- ZipList
------------------------------------------------------------------------------

-- | Just like 'List' except that it has a zipping 'Applicative' instance
-- and no 'Monad' instance.
--
-- @since 0.6.0
newtype ZipList a = ZipList { toZipSerial :: ZipSerialM Identity a }
    deriving (Show, Read, Eq, Ord, NFData
#if MIN_VERSION_deepseq(1,4,3)
    , NFData1
#endif
             , Semigroup, Monoid, Functor, Foldable
             , Applicative, Traversable)

instance (a ~ Char) => IsString (ZipList a) where
    {-# INLINE fromString #-}
    fromString = ZipList . P.fromList

-- GHC versions 8.0 and below cannot derive IsList
instance IsList (ZipList a) where
    type (Item (ZipList a)) = a
    {-# INLINE fromList #-}
    fromList = ZipList . P.fromList
    {-# INLINE toList #-}
    toList = runIdentity . P.toList . toZipSerial

-- | Convert a 'ZipList' to a regular 'List'
--
-- @since 0.6.0
fromZipList :: ZipList a -> List a
fromZipList = List . K.adapt . toZipSerial

-- | Convert a regular 'List' to a 'ZipList'
--
-- @since 0.6.0
toZipList :: List a -> ZipList a
toZipList = ZipList . K.adapt . toSerial