packages feed

primus-0.1.0.0: src/Primus/AsMaybe.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

{- |
Module      : Primus.AsMaybe
Description : methods with termination
Copyright   : (c) Grant Weyburne, 2022
License     : BSD-3
-}
module Primus.AsMaybe (
  -- * AsMaybe
  AsMaybe (..),
  iterateT1,
  unfoldrT,
  pairsT,

  -- * ApThese
  ApThese (..),
  toTheseT,
  toTheseTS,
  partitionEithersT,
  partitionTheseT,
  filterT,
  spanT,
  spanTAlt,
  spanTS,
  takeWhileT,
  takeWhileTS,

  -- * ApTheseF for use with 'Primus.LRHist.LRHist'
  ApTheseF (..),
) where

import Control.Arrow
import Data.Bool
import Data.Functor.Identity
import qualified Data.List as L
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Semigroup as SG
import Data.These
import Data.These.Combinators
import Primus.Extra

-- | converts to a 'Maybe' for failure types
class AsMaybe x b | x -> b where
  toMaybe :: x -> Maybe b

instance (b ~ b1) => AsMaybe (These e b) b1 where
  toMaybe = these (const Nothing) Just (const Just)
instance (b ~ b1) => AsMaybe (Either e b) b1 where
  toMaybe = either (const Nothing) Just
instance (b ~ b1) => AsMaybe (Maybe b) b1 where
  toMaybe = id
instance (b1 ~ [b]) => AsMaybe [b] b1 where
  toMaybe = \case
    [] -> Nothing
    as@(_ : _) -> Just as
instance (z ~ SG.Arg b1 y, AsMaybe x b1) => AsMaybe (SG.Arg x y) z where
  toMaybe (SG.Arg x y) = (`SG.Arg` y) <$> toMaybe x
instance (b ~ (b1, b2), AsMaybe x b1, AsMaybe y b2) => AsMaybe (x, y) b where
  toMaybe (x, y) = (,) <$> toMaybe x <*> toMaybe y
instance (b ~ (b1, b2, b3), AsMaybe x b1, AsMaybe y b2, AsMaybe z b3) => AsMaybe (x, y, z) b where
  toMaybe (x, y, z) = (,,) <$> toMaybe x <*> toMaybe y <*> toMaybe z

instance AsMaybe x z => AsMaybe (Identity x) z where
  toMaybe (Identity x) = toMaybe x

-- supports Bool instance so partition can work the same as base [not a requirement but..]

-- | flexible "e" to use with eg 'partitionTheseT': Bool is also valid
class ApThese e a x b | x e a -> b where
  apThese :: a -> x -> These e b

instance (e ~ e1, b ~ b1) => ApThese e1 a (These e b) b1 where
  apThese _ = id
instance (e ~ e1, b ~ b1) => ApThese e1 a (Either e b) b1 where
  apThese _ = either This That
instance (e ~ a, b ~ b1) => ApThese e a (Maybe b) b1 where
  apThese a = maybe (This a) That
instance (e ~ a, b ~ a) => ApThese e a Bool b where
  apThese a = bool (This a) (That a)
instance (e ~ a, b1 ~ [b]) => ApThese e a [b] b1 where
  apThese a = \case
    [] -> This a
    as@(_ : _) -> That as

instance (z ~ SG.Arg b1 y, ApThese e a x b1) => ApThese e a (SG.Arg x y) z where
  apThese a (SG.Arg x y) = (`SG.Arg` y) <$> apThese a x
instance (Semigroup e, b ~ (b1, b2), ApThese e a x b1, ApThese e a y b2) => ApThese e a (x, y) b where
  apThese a (x, y) = (,) <$> apThese a x <*> apThese a y
instance (Semigroup e, b ~ (b1, b2, b3), ApThese e a x b1, ApThese e a y b2, ApThese e a z b3) => ApThese e a (x, y, z) b where
  apThese a (x, y, z) = (,,) <$> apThese a x <*> apThese a y <*> apThese a z

instance ApThese e a x z => ApThese e a (Identity x) z where
  apThese a (Identity x) = apThese a x

-- for LRHist "e" is fixed
-- supports Bool instance for use with LRHist [this is a requirement]

-- | for use with 'Primus.LRHist.LRHist' using a fixed "e"
class ApTheseF e a x b | x e a -> b where
  apTheseF :: a -> x -> These e b

instance (e ~ e1, b ~ b1) => ApTheseF e1 a (These e b) b1 where
  apTheseF _ = id
instance (e ~ e1, b ~ b1) => ApTheseF e1 a (Either e b) b1 where
  apTheseF _ = either This That
instance (Monoid e, b ~ b1) => ApTheseF e a (Maybe b) b1 where
  apTheseF _ = maybe (This mempty) That
instance (Monoid e, b ~ a) => ApTheseF e a Bool b where
  apTheseF a = bool (This mempty) (That a)
instance (Monoid e, b1 ~ [b]) => ApTheseF e a [b] b1 where
  apTheseF _ = \case
    [] -> This mempty
    as@(_ : _) -> That as

instance (z ~ SG.Arg b1 y, ApTheseF e a x b1) => ApTheseF e a (SG.Arg x y) z where
  apTheseF a (SG.Arg x y) = (`SG.Arg` y) <$> apTheseF a x
instance (Semigroup e, b ~ (b1, b2), ApTheseF e a x b1, ApTheseF e a y b2) => ApTheseF e a (x, y) b where
  apTheseF a (x, y) = (,) <$> apTheseF a x <*> apTheseF a y
instance (Semigroup e, b ~ (b1, b2, b3), ApTheseF e a x b1, ApTheseF e a y b2, ApTheseF e a z b3) => ApTheseF e a (x, y, z) b where
  apTheseF a (x, y, z) = (,,) <$> apTheseF a x <*> apTheseF a y <*> apTheseF a z

instance ApTheseF e a x z => ApTheseF e a (Identity x) z where
  apTheseF a (Identity x) = apTheseF a x

-- | similar to 'Data.List.NonEmpty.iterate' but terminate using 'AsMaybe'
iterateT1 ::
  AsMaybe x a =>
  (a -> x) ->
  a ->
  NonEmpty a
iterateT1 f a0 = a0 :| go a0
 where
  go a = case toMaybe (f a) of
    Nothing -> []
    Just x -> x : go x


{- | like 'Data.List.unfoldr' but terminate using 'AsMaybe'

@
>>> unfoldrT (splitAt 2) [1..8]
[[1,2],[3,4],[5,6],[7,8]]

vs

>>> unfoldr (\s -> if null s then Nothing else Just (splitAt 2 s)) [1..8]
[[1,2],[3,4],[5,6],[7,8]]
@
-}
unfoldrT ::
  AsMaybe t t =>
  (t -> (a, t)) ->
  t ->
  [a]
unfoldrT f s0 =
  case toMaybe s0 of
    Nothing -> []
    Just s1 ->
      let (a, s2) = f s1
       in a : unfoldrT f s2

-- | run a functions against each side of a tuple and stitch them together for use with 'unfoldrT' where "s" is a tuple and you want to stop as soon as the either terminates
pairsT :: (x -> (a, x)) -> (y -> (b, y)) -> (x, y) -> ((a, b), (x, y))
pairsT f g (x0, y0) =
  let (a, x) = f x0
      (b, y) = g y0
   in ((a, b), (x, y))

-- | apply a function to a list and convert to a list of 'These'
toTheseT ::
  forall e a x b.
  (ApThese e a x b) =>
  (a -> x) ->
  [a] ->
  [These e b]
toTheseT f = map (\a -> apThese a (f a))

-- | like 'toTheseT' with state
toTheseTS ::
  forall e a x b z.
  (ApThese e a x b) =>
  (z -> a -> (z, x)) ->
  z ->
  [a] ->
  (z, [These e b])
toTheseTS f = L.mapAccumL (\z a -> second (apThese a) (f z a))

-- | like 'partitionEithersT' ignoring the second element of the result
filterT ::
  forall e a b x.
  ApThese e a x b =>
  (a -> x) ->
  [a] ->
  [b]
filterT = catThat .@ toTheseT @e -- minimal type applications required as "e" isnt used here

-- | like 'toTheseT' but use 'partitionHereThere' on the results (swapped version of 'Data.List.partition')
partitionEithersT ::
  forall e a b x.
  ApThese e a x b =>
  (a -> x) ->
  [a] ->
  ([e], [b])
partitionEithersT = partitionHereThere .@ toTheseT

-- | like 'toTheseT' but use 'partitionThese' on the results
partitionTheseT ::
  forall e a b x.
  ApThese e a x b =>
  (a -> x) ->
  [a] ->
  ([e], [b], [(e, b)])
partitionTheseT = partitionThese .@ toTheseT

-- | similar to 'Data.List.span' using 'ApThese' for failure (support Bool and These)
spanT ::
  forall e a x b.
  ApThese e a x b =>
  (a -> x) ->
  [a] ->
  ([b], [a])
spanT f = \case
  [] -> ([], [])
  a : as -> case apThese @e a (f a) of
    This _ -> ([], a : as)
    That b -> first (b :) (spanT @e f as)
    These _ b -> ((b :) *** (a :)) (spanT @e f as) -- put in both buckets and keep going

-- | like 'spanT' but doesn't continue in the 'These' case
spanTAlt ::
  forall e a x b.
  ApThese e a x b =>
  (a -> x) ->
  [a] ->
  ([b], [a])
spanTAlt f = \case
  [] -> ([], [])
  a : as -> case apThese @e a (f a) of
    This _ -> ([], a : as)
    That b -> first (b :) (spanT @e f as)
    These _ b -> ([b], a : as) -- put in both buckets and stop

-- | like 'spanT' with state
spanTS ::
  forall e a x b z.
  ApThese e a x b =>
  (z -> a -> (z, x)) ->
  z ->
  [a] ->
  (z, ([b], [a]))
spanTS f z0 = \case
  [] -> (z0, ([], []))
  a : as ->
    let (z, x) = f z0 a
     in case apThese @e a x of
          This _ -> (z, ([], a : as))
          That b -> second (first (b :)) (spanTS @e f z as)
          These _ b -> second ((b :) *** (a :)) (spanTS @e f z as) -- if these then put in both buckets

-- | like 'takeWhileT' with state
takeWhileTS ::
  forall e a x b z.
  ApThese e a x b =>
  (z -> a -> (z, x)) ->
  z ->
  [a] ->
  (z, [b])
takeWhileTS f = second fst .@ spanTS @e f

-- | like 'spanT' but ignore the second element of the result
takeWhileT ::
  forall e a x b.
  ApThese e a x b =>
  (a -> x) ->
  [a] ->
  [b]
takeWhileT = fst .@ spanT @e