packages feed

sdp-0.2.1: src/SDP/Indexed.hs

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

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

{- |
    Module      :  SDP.Indexed
    Copyright   :  (c) Andrey Mulik 2019-2021
    License     :  BSD-style
    Maintainer  :  work.a.mulik@gmail.com
    Portability :  non-portable (GHC extensions)
  
  "SDP.Indexed" provides 'Indexed' and 'Freeze' classes.
-}
module SDP.Indexed
(
  -- * Exports
  module SDP.Linear,
  module SDP.Map,
  
  -- * Indexed
  Indexed (..), Indexed1, Indexed2, binaryContain,
  
  -- * Freeze
  Freeze (..), Freeze1, Freeze2,
  
#if __GLASGOW_HASKELL__ >= 806
  -- ** Rank 2 quantified constraints
  -- | GHC 8.6.1+ only
  Indexed', Indexed'', Freeze', Freeze''
#endif
)
where

import Prelude ()
import SDP.SafePrelude
import SDP.Linear
import SDP.Map

import Control.Exception.SDP

default ()

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

{-# WARNING updates' "deprecated in favor of 'update', will be removed in sdp-0.3" #-}
{-# WARNING update'  "will be moved to SDP.Map.Map class in sdp-0.3" #-}
{-# WARNING write'   "will be moved to SDP.Map.Map class in sdp-0.3" #-}

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

-- | 'Indexed' is class of ordered associative arrays with static bounds.
class (Linear v e, Bordered v i, Map v i e) => Indexed v i e | v -> i, v -> e
  where
    {-# MINIMAL fromIndexed #-}
    
    {- |
      @assoc bnds ascs@ create 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).
    -}
    assoc :: (i, i) -> [(i, e)] -> v
    assoc =  flip assoc' (undEx "assoc {default}")
    
    {- |
      @assoc' bnds def 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).
    -}
    assoc' :: (i, i) -> e -> [(i, e)] -> v
    assoc' bnds defvalue = toMap' defvalue . filter (inRange bnds . fst)
    
    -- | 'fromIndexed' converts this indexed structure to another one.
    fromIndexed :: (Indexed m j e) => m -> v
    
    -- | Safe index-based immutable writer.
    {-# INLINE write' #-}
    write' :: v -> i -> e -> v
    write' es = write es . offsetOf es
    
    -- | Update element by given function.
    update' :: v -> (e -> e) -> i -> v
    update' es f i = write' es i . f $ es!i
    
    -- | Create new structure from old by mapping with index.
    updates' :: v -> (i -> e -> e) -> v
    updates' es f = bounds es `assoc` [ (i, f i e) | (i, e) <- assocs es ]
    
    {- |
      @'accum' f es ies@ create a new structure from @es@ elements selectively
      updated by function @f@ and @ies@ associations list.
    -}
    accum :: (e -> e' -> e) -> v -> [(i, e')] -> v
    accum f es ies = bounds es `assoc` [ (i, es!i `f` e') | (i, e') <- ies ]
    
    -- | 'imap' creates new indexed structure from old with reshaping.
    imap :: (Map m j e) => (i, i) -> m -> (i -> j) -> v
    imap bnds es f = assoc bnds [ (i, es!f i) | i <- range bnds ]

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

-- | Service class of mutable to immutable conversions.
class (Monad m) => Freeze m v' v | v' -> m
  where
    {- |
      @freeze@ is a safe way to convert a mutable structure to a immutable.
      @freeze@ should copy the old structure or ensure that it will not be used
      after calling the procedure.
    -}
    freeze :: v' -> m v
    
    {- |
      @unsafeFreeze@ is unsafe version of 'freeze'. @unsafeFreeze@ 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.
    -}
    unsafeFreeze :: v' -> m v
    unsafeFreeze =  freeze

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

-- | 'Indexed' contraint for @(Type -> Type)@-kind types.
type Indexed1 v i e = Indexed (v e) i e

-- | 'Indexed' contraint for @(Type -> Type -> Type)@-kind types.
type Indexed2 v i e = Indexed (v i e) i e

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

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

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

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

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

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

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

instance Indexed [e] Int e
  where
    assoc' bnds e = toMap' e . filter (inRange bnds . fst)
    
    fromIndexed es = (es !) <$> indices es

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

-- | binaryContain checks that sorted structure has equal element.
binaryContain :: (Linear v e, Bordered v i) => Compare e -> e -> v -> Bool
binaryContain _ _ Z  = False
binaryContain f e es =
  let
    contain l u = not (l > u) && case f e (es !^ j) of
        LT -> contain l (j - 1)
        EQ -> True
        GT -> contain (j + 1) u
      where
        j = u - l `div` 2 + l
  in  f e (head es) /= LT && f e (last es) /= GT && contain 0 (sizeOf es - 1)

undEx :: String -> a
undEx =  throw . UndefinedValue . showString "in SDP.Indexed."