packages feed

sdp-0.2.1: src/SDP/IndexedM.hs

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, DefaultSignatures #-}
{-# LANGUAGE Safe, CPP, ConstraintKinds #-}

#if __GLASGOW_HASKELL__ >= 806
{-# LANGUAGE QuantifiedConstraints, RankNTypes #-}
#endif

{- |
    Module      :  SDP.IndexedM
    Copyright   :  (c) Andrey Mulik 2019-2021
    License     :  BSD-style
    Maintainer  :  work.a.mulik@gmail.com
    Portability :  non-portable (GHC extensions)
    
    "SDP.IndexedM" provides 'IndexedM' and 'Thaw' classes.
-}
module SDP.IndexedM
(
  -- * Exports
  module SDP.LinearM,
  module SDP.Indexed,
  module SDP.MapM,
  
  -- * IndexedM
  IndexedM (..), IndexedM1, IndexedM2,
  
  -- * Thaw
  Thaw (..), Thaw1, Thaw2,
  
#if __GLASGOW_HASKELL__ >= 806
  -- ** Rank 2 quantified constraints
  -- | GHC 8.6.1+ only
  IndexedM', IndexedM'', Thaw', Thaw''
#endif
)
where

import Prelude ()
import SDP.SafePrelude
import SDP.LinearM
import SDP.Indexed
import SDP.MapM

import Control.Exception.SDP

default ()

--------------------------------------------------------------------------------

{-# WARNING updateM' "will be moved to SDP.MapM.MapM class in sdp-0.3" #-}
{-# WARNING writeM'  "will be moved to SDP.MapM.MapM class in sdp-0.3" #-}

--------------------------------------------------------------------------------

-- | Class for work with mutable indexed structures.
class (LinearM m v e, BorderedM m v i, MapM m v i e) => IndexedM m v i e
  where
    {-# MINIMAL fromIndexed', fromIndexedM #-}
    
    {- |
      @fromAssocs bnds ascs@ creates new structure from list of associations,
      without default element. Note that @bnds@ is @ascs@ bounds and may not
      match with the result bounds (not always possible).
    -}
    fromAssocs :: (i, i) -> [(i, e)] -> m v
    fromAssocs =
      let err = throw $ UndefinedValue "in SDP.IndexedM.fromAssocs {default}"
      in  flip fromAssocs' err
    
    {- |
      @fromAssocs' bnds defvalue ascs@ creates new structure from list of
      associations, with default element. Note that @bnds@ is @ascs@ bounds and
      may not match with the result bounds (not always possible).
    -}
    fromAssocs' :: (i, i) -> e -> [(i, e)] -> m v
    fromAssocs' bnds defvalue = newMap' defvalue . filter (inRange bnds . fst)
    
    {- |
      @'writeM' map key e@ writes element @e@ to @key@ position safely (if @key@
      is out of @map@ range, do nothing). The 'writeM' function is intended to
      overwrite only existing values, so its behavior is identical for
      structures with both static and dynamic boundaries.
    -}
    writeM' :: v -> i -> e -> m ()
    writeM' es i e = do bnds <- getBounds es; writeM es (offset bnds i) e
    
    -- | Just swap two elements.
    swapM' :: v -> i -> i -> m ()
    swapM' es i j = do ei <- es >! i; writeM' es i =<< es >! j; writeM' es j ei
    
    -- | fromIndexed' is overloaded version of thaw.
    fromIndexed' :: (Indexed v' j e) => v' -> m v
    
    -- | fromIndexed converts one mutable structure to other.
    fromIndexedM :: (IndexedM m v' j e) => v' -> m v
    
    -- | reshaped creates new indexed structure from old with reshaping function.
    reshaped :: (IndexedM m v' j e) => (i, i) -> v' -> (i -> j) -> m v
    reshaped bnds es f = fromAssocs bnds =<< range bnds `forM` \ i -> (,) i <$> es !> f i
    
    {- |
      @'fromAccum' f es ies@ create a new structure from @es@ elements
      selectively updated by function @f@ and @ies@ associations list.
    -}
    fromAccum :: (e -> e' -> e) -> v -> [(i, e')] -> m v
    fromAccum f es ascs = getBounds es >>=<< ies $ fromAssocs
      where
        ies = sequence [ do e <- es !> i; return (i, f e e') | (i, e') <- ascs ]
    
    -- | Update element by given function.
    updateM' :: v -> (e -> e) -> i -> m ()
    updateM' es f i = writeM' es i . f =<< es >! i

--------------------------------------------------------------------------------

-- | Service class of immutable to mutable conversions.
class (Monad m) => Thaw m v v' | v' -> m
  where
    {- |
      @thaw@ is safe way to convert a immutable structure to a mutable. @thaw@
      should copy the old structure or ensure that it will not be used after the
      procedure calling.
    -}
    thaw :: v -> m v'
    
    {- |
      @unsafeThaw@ is unsafe version of 'thaw'. @unsafeThaw@ doesn't guarantee
      that the structure will be copied or locked. It only guarantees that if
      the old structure isn't used, no error will occur.
    -}
    unsafeThaw :: v -> m v'
    unsafeThaw =  thaw

-- | 'IndexedM' contraint for @(Type -> Type)@-kind types.
type IndexedM1 m v i e = IndexedM m (v e) i e

-- | 'IndexedM' contraint for @(Type -> Type -> Type)@-kind types.
type IndexedM2 m v i e = IndexedM m (v i e) i e

-- | 'Thaw' contraint for @(Type -> Type)@-kind types.
type Thaw1 m v v' e = Thaw m (v e) (v' e)

-- | 'Thaw' contraint for @(Type -> Type -> Type)@-kind types.
type Thaw2 m v v' i e = Thaw m (v i e) (v' i e)

#if __GLASGOW_HASKELL__ >= 806
-- | 'IndexedM' contraint for @(Type -> Type)@-kind types.
type IndexedM' m v i = forall e . IndexedM m (v e) i e

-- | 'IndexedM' contraint for @(Type -> Type -> Type)@-kind types.
type IndexedM'' m v = forall i e . IndexedM m (v i e) i e

-- | 'Thaw' contraint for @(Type -> Type)@-kind types.
type Thaw' m v v' = forall e . Thaw m (v e) (v' e)

-- | 'Thaw' contraint for @(Type -> Type -> Type)@-kind types.
type Thaw'' m v v' = forall i e . Thaw m (v i e) (v' i e)
#endif