packages feed

BPS-0.1.0.0: src/main/haskell/lib/Data/TMS/MList.hs

{-|
Module      : MList
Description : Mutable linked lists in STT
Copyright   : (c) John Maraist, 2022
License     : AllRightsReserved
Maintainer  : haskell-tms@maraist.org
Stability   : experimental
Portability : POSIX

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, for NON-COMMERCIAL use.  See the License for the specific
language governing permissions and limitations under the License.

-}

{-# LANGUAGE RankNTypes #-}

module Data.TMS.MList where

import Control.Monad.State
import Control.Monad.ST.Trans
import Control.Monad.Except
import Control.Monad.Extra

-- * Mutable lists (cons cells) in `STT`

-- |Singly linked lists!  But with mutable CARs and CDRs à la Common
-- Lisp.
data MList s a = MCons (STRef s a) (STRef s (MList s a))
                 -- ^ A @cons@ cell with mutable fields.
               | MNil
                 -- ^ Regular old @nil@.

-- |Convert a pure list into a mutable list.
toMList :: Monad m => [a] -> STT s m (MList s a)
toMList [] = return MNil
toMList (x : xs) = do
  car <- newSTRef x
  cdrBody <- toMList xs
  cdr <- newSTRef cdrBody
  return $ MCons car cdr

-- |Convert an `MList` to a `String`.
showM :: (Show a, Monad m) => MList s a -> STT s m String
showM MNil = return "[]"
showM (MCons xr xsr) = do
  x <- readSTRef xr
  xs <- readSTRef xsr
  let sx = show x
  sxs <- showM xs
  return $ sx ++ " m: " ++ sxs

-- |Returns `True` for an empty list.
mnull MNil = True
mnull _ = False

-- |Returns `True` from an `STT` monad for a reference to an empty
-- list.
getMnull :: Monad m => STRef s (MList s a) -> STT s m Bool
getMnull ref = readSTRef ref >>= return . mnull

-- |Returns the CAR (element) of the first CONS cell of a non-empty
-- mutable list.
mcar (MCons x _)  = readSTRef x
-- |Returns the CDR (next cell) of the first CONS cell of a non-empty
-- mutable list.
mcdr (MCons _ xs) = readSTRef xs

-- |Convert a traditional Haskell list into a mutable `MList` list.
mlength :: Monad m => MList s a -> STT s m Int
mlength MNil = return 0
mlength (MCons _ xs) = do
  cdr <- readSTRef xs
  cdrLen <- mlength cdr
  return $ 1 + cdrLen

-- |Convert a traditional Haskell list into a mutable `MList` list.
fromList :: Monad m => [a] -> STT s m (MList s a)
fromList [] = return MNil
fromList (x : xs) = do
  car <- newSTRef x
  tail <- fromList xs
  cdr <- newSTRef tail
  return $ MCons car cdr

-- |Convert a traditional Haskell list into a mutable `MList` list,
-- applying the given function to each element.
fromListMap :: Monad m => (a -> b) -> [a] -> STT s m (MList s b)
fromListMap _ [] = return MNil
fromListMap f (x : xs) = do
  car <- newSTRef $ f x
  tail <- fromListMap f xs
  cdr <- newSTRef tail
  return $ MCons car cdr

-- |Convert a mutable `MList` list into a traditional Haskell list.
toList :: Monad m => MList s a -> STT s m [a]
toList MNil = return []
toList (MCons car cdr) = do
  x <- readSTRef car
  ms <- readSTRef cdr
  xs <- toList ms
  return $ x : xs

-- |Convert a mutable `MList` list of `Maybe` values into a
-- traditional Haskell list containing only the values under a `Just`
-- constructor.
toUnmaybeList :: Monad m => MList s (Maybe a) -> STT s m [a]
toUnmaybeList MNil = return []
toUnmaybeList (MCons car cdr) = do
  xmaybe <- readSTRef car
  ms <- readSTRef cdr
  xs <- toUnmaybeList ms
  case xmaybe of
    Nothing -> return xs
    Just x -> return $ x : xs

-- |A version of @map@ for `MList`s.
mlistMap :: Monad m => (a -> b) -> MList s a -> STT s m (MList s b)
mlistMap f MNil = return MNil
mlistMap f (MCons xref xsref) = do
  x <- readSTRef xref
  xs <- readSTRef xsref
  xref' <- newSTRef $ f x
  xs' <- mlistMap f xs
  xsref' <- newSTRef xs'
  return $ MCons xref' xsref'

-- |A version of @filter@ for `MList`s.
mlistFilter :: Monad m => (a -> Bool) -> MList s a -> STT s m (MList s a)
mlistFilter p l = do
  (_, result) <- flt p l
  return result
  where
    flt :: Monad m => (a -> Bool) -> MList s a -> STT s m (Bool, MList s a)
    flt pred l@MNil = return (False, l)
    flt pred l@(MCons xref xsref) = do
      x <- readSTRef xref
      xs <- readSTRef xsref
      (changed, xs') <- flt pred xs
      if pred x
      then if changed
           then do
             xsref' <- newSTRef xs'
             return (True, MCons xref xsref')
           else return (False, l)
      else return (True, xs')

-- |Return a new `MList` which strips off the `Just` constructor from
-- its elements, dropping and elements which are `Nothing`.
mlistUnmaybe :: Monad m => MList s (Maybe a) -> STT s m (MList s a)
mlistUnmaybe MNil = return MNil
mlistUnmaybe (MCons xref xsref) = do
  x <- readSTRef xref
  xs <- readSTRef xsref
  xs' <- mlistUnmaybe xs
  case x of
    Nothing -> return xs'
    Just x' -> do
      xref' <- newSTRef x'
      xsref' <- newSTRef xs'
      return $ MCons xref' xsref'

-- |Return a new `MList` which drops elements which are `Nothing`.
mlistStripNothing :: Monad m => MList s (Maybe a) -> STT s m (MList s (Maybe a))
mlistStripNothing = mlistFilter (not . null)

-- |Return a new `MList` which drops elements which are `Nothing` from
-- the `MList` under the reference argument.
getMlistStripNothing ::
  Monad m => STRef s (MList s (Maybe a)) -> STT s m (MList s (Maybe a))
getMlistStripNothing ref = do
  mlist <- readSTRef ref
  mlistFilter (not . null) mlist

-- |Treating an `MList` as a stack, add a new element at the top of
-- the stack, and return the new stack top.
mlistPush :: Monad m => a -> MList s a -> STT s m (MList s a)
mlistPush item mlist = do
  itemRef <- newSTRef item
  mlistRef <- newSTRef mlist
  return $ MCons itemRef mlistRef

-- |Treating an `MList` as a stack, add a new element at the top of
-- the stack, and return the new stack top.
mlistRefPush :: Monad m => a -> STRef s (MList s a) -> STT s m ()
mlistRefPush item mlistRef = do
  carRef <- newSTRef item
  cdr <- readSTRef mlistRef
  newCdrRef <- newSTRef cdr
  let newCons = MCons carRef newCdrRef
  writeSTRef mlistRef newCons

-- |Iterate over the elements of a `MList`.  The body does not
-- necessarily need operate in the same monad as where the references
-- originate; the @lifter@ parameter brings the latter into the
-- former.
mlistFor_ :: (Monad m0, Monad m) =>
  (forall r . STT s m0 r -> m r) -> MList s a -> (a -> m ()) -> m ()
mlistFor_ lifter MNil _ = return ()
mlistFor_ lifter (MCons xref xsref) bodyf = do
  x <- lifter $ readSTRef xref
  bodyf x
  xs <- lifter $ readSTRef xsref
  mlistFor_ lifter xs bodyf

-- |Like `mlistFor_`, but the body expects an `MCons` cell instead of
-- the list element itself.  Useful for mutating the list along the
-- way.
mlistForCons_ :: (Monad m0, Monad m) =>
  (forall r . STT s m0 r -> m r) -> MList s a -> (MList s a -> m ()) -> m ()
mlistForCons_ _ MNil _ = return ()
mlistForCons_ lifter mc@(MCons _ _) bodyf = do
  bodyf mc
  xs <- lifter $ mcdr mc
  mlistForCons_ lifter xs bodyf

-- |A combination of `mlistForCons_` and
-- `Data.TMS.Helpers.forMwhile_`: iterate over the `MCons` cell of a
-- list, with a trigger for an early exit.  Note that the monad for
-- the continuation condition is over the overall monad @m@, not the
-- `STT` wrapped monad @m0@.
mlistForConsWhile_ ::
  (Monad m0, Monad m) =>
    (forall r . STT s m0 r -> m r) -> MList s a -> m Bool -> (MList s a -> m ())
      -> m ()
mlistForConsWhile_ _ MNil _ _ = return ()
mlistForConsWhile_ lifter mc@(MCons _ _) moreM bodyf =
  whenM moreM $ do
    bodyf mc
    xs <- lifter $ mcdr mc
    mlistForConsWhile_ lifter xs moreM bodyf

-- |Overwrite the @car@ slot of the given `MCons` with the given
-- value.  Named after the Common Lisp function with the same
-- behavior.
rplaca :: Monad m => MList s a -> a -> STT s m ()
rplaca (MCons r _) v = writeSTRef r v