{-# LANGUAGE BangPatterns #-}

{- |
Copyright:  (c) 2021 Kowainik
SPDX-License-Identifier: MPL-2.0
Maintainer:  Kowainik <xrom.xkov@gmail.com>
Stability:   Stable
Portability: Portable

Useful 'Maybe' combinators to work with the 'Maybe' data type and 'Slist'
together.

@since 0.2.0.0
-}
module Slist.Maybe
    ( maybeToSlist
    , slistToMaybe
    , catMaybes
    , mapMaybe
    , slistWith
    ) where

import Data.Bifunctor (second)

import Slist.Size (Size (..))
import Slist.Type (Slist (..), cons, one)

import qualified Data.Maybe as M


{- | Returns an empty list when given 'Nothing' or a singleton list when given
'Just'.

>>> maybeToSlist (Just 42)
Slist {sList = [42], sSize = Size 1}
>>> maybeToSlist Nothing
Slist {sList = [], sSize = Size 0}

@since 0.2.0.0
-}
maybeToSlist :: Maybe a -> Slist a
maybeToSlist :: Maybe a -> Slist a
maybeToSlist = Slist a -> (a -> Slist a) -> Maybe a -> Slist a
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Slist a
forall a. Monoid a => a
mempty a -> Slist a
forall a. a -> Slist a
one
{-# INLINE maybeToSlist #-}

{- | Returns 'Nothing' on an empty list or @'Just' a@ where @a@ is the first
element of the slist.

==== __Examples__

Basic usage:

>>> slistToMaybe mempty
Nothing

>>> slistToMaybe (one 42)
Just 42

>>> slistToMaybe (cons 1 $ cons 2 $ one 3)
Just 1

__Laws__ :

@
slistToMaybe . maybeToList ≡ id
@

Reverse is right only on singleton/empty lists

@
maybeToList . slistToMaybe {empty, singleton slist} ≡ {empty, singleton slist}
@

@since 0.2.0.0
-}
slistToMaybe :: Slist a -> Maybe a
slistToMaybe :: Slist a -> Maybe a
slistToMaybe = (a -> Maybe a -> Maybe a) -> Maybe a -> Slist a -> Maybe a
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (Maybe a -> Maybe a -> Maybe a
forall a b. a -> b -> a
const (Maybe a -> Maybe a -> Maybe a)
-> (a -> Maybe a) -> a -> Maybe a -> Maybe a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Maybe a
forall a. a -> Maybe a
Just) Maybe a
forall a. Maybe a
Nothing
{-# INLINE slistToMaybe #-}
-- We define listToMaybe using foldr so that it can fuse via the foldr/build
-- rule. See #14387


{- | Takes a slist of 'Maybe's and returns a slist of all the 'Just' values.

>>> catMaybes (cons (Just 1) $ cons Nothing $ one $ Just 3)
Slist {sList = [1,3], sSize = Size 2}

@since 0.2.0.0
-}
catMaybes :: Slist (Maybe a) -> Slist a
catMaybes :: Slist (Maybe a) -> Slist a
catMaybes = (Maybe a -> Maybe a) -> Slist (Maybe a) -> Slist a
forall b a. (a -> Maybe b) -> Slist a -> Slist b
mapMaybe Maybe a -> Maybe a
forall a. a -> a
id
{-# INLINE catMaybes #-}

{- | The 'Maybe' version of 'map' which can throw out elements.

If appliying the given function returns 'Nothing', no element is added on to the
result list. If it is @'Just' b@, then @b@ is included in the result list.

>>> maybeEven x = if even x then Just x else Nothing
>>> s = cons 1 $ cons 2 $ one 3

>>> mapMaybe maybeEven s
Slist {sList = [2], sSize = Size 1}

If we map the 'Just' constructor, the entire list should be returned:

>>> mapMaybe Just s
Slist {sList = [1,2,3], sSize = Size 3}

@since 0.2.0.0
-}
mapMaybe :: forall b a . (a -> Maybe b) -> Slist a -> Slist b
mapMaybe :: (a -> Maybe b) -> Slist a -> Slist b
mapMaybe a -> Maybe b
_ (Slist [] Size
_) = Slist b
forall a. Monoid a => a
mempty
mapMaybe a -> Maybe b
f (Slist [a]
xs Size
Infinity) = [b] -> Size -> Slist b
forall a. [a] -> Size -> Slist a
Slist ((a -> Maybe b) -> [a] -> [b]
forall a b. (a -> Maybe b) -> [a] -> [b]
M.mapMaybe a -> Maybe b
f [a]
xs) Size
Infinity
mapMaybe a -> Maybe b
f (Slist (a
x:[a]
xs) Size
n) = case a -> Maybe b
f a
x of
    Maybe b
Nothing -> Slist b
rest
    Just b
r  -> b -> Slist b -> Slist b
forall a. a -> Slist a -> Slist a
cons b
r Slist b
rest
  where
    rest :: Slist b
    rest :: Slist b
rest = (a -> Maybe b) -> Slist a -> Slist b
forall b a. (a -> Maybe b) -> Slist a -> Slist b
mapMaybe a -> Maybe b
f ([a] -> Size -> Slist a
forall a. [a] -> Size -> Slist a
Slist [a]
xs (Size
n Size -> Size -> Size
forall a. Num a => a -> a -> a
- Size
1))
{-# NOINLINE [1] mapMaybe #-}

{- | Similar to 'mapMaybe' but works with the ordinary list as the input:

>>> maybeEven x = if even x then Just x else Nothing

>>> slistWith maybeEven [1,2,3]
Slist {sList = [2], sSize = Size 1}

@since 0.2.0.0
-}
slistWith :: forall b a . (a -> Maybe b) -> [a] -> Slist b
slistWith :: (a -> Maybe b) -> [a] -> Slist b
slistWith a -> Maybe b
f [a]
l = let (Int
n, [b]
sl) = Int -> [a] -> (Int, [b])
go Int
0 [a]
l in [b] -> Size -> Slist b
forall a. [a] -> Size -> Slist a
Slist [b]
sl (Int -> Size
Size Int
n)
  where
    go :: Int -> [a] -> (Int, [b])
    go :: Int -> [a] -> (Int, [b])
go !Int
accSize [] = (Int
accSize, [])
    go !Int
accSize (a
x:[a]
xs) = case a -> Maybe b
f a
x of
        Maybe b
Nothing -> Int -> [a] -> (Int, [b])
go Int
accSize [a]
xs
        Just b
r  -> ([b] -> [b]) -> (Int, [b]) -> (Int, [b])
forall (p :: * -> * -> *) b c a.
Bifunctor p =>
(b -> c) -> p a b -> p a c
second (b
rb -> [b] -> [b]
forall a. a -> [a] -> [a]
:) ((Int, [b]) -> (Int, [b])) -> (Int, [b]) -> (Int, [b])
forall a b. (a -> b) -> a -> b
$ Int -> [a] -> (Int, [b])
go (Int
accSize Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) [a]
xs

-- {-# RULES
-- "mapMaybe"     [~1] forall f xs. mapMaybe f xs
--                     = build (\c n -> foldr (mapMaybeFB c f) n xs)
-- "mapMaybeList" [1]  forall f. foldr (mapMaybeFB (:) f) [] = mapMaybe f
--   #-}
-- {-# INLINE [0] mapMaybeFB #-} -- See Note [Inline FB functions] in GHC.List
-- mapMaybeFB :: (b -> r -> r) -> (a -> Maybe b) -> a -> r -> r
-- mapMaybeFB cons f x next = case f x of
--   Nothing -> next
--   Just r -> cons r next