{-# LANGUAGE TemplateHaskell #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Control.Effect.Map
-- Copyright   :  (c) Michael Szvetits, 2020
-- License     :  BSD3 (see the file LICENSE)
-- Maintainer  :  typedbyte@qualified.name
-- Stability   :  stable
-- Portability :  portable
--
-- The map effect for modeling a mutable collection of key-value pairs.
--
-- Lazy and strict interpretations of the effect are available here:
-- "Control.Effect.Map.Lazy" and "Control.Effect.Map.Strict".
-----------------------------------------------------------------------------
module Control.Effect.Map
  ( -- * Tagged Map Effect
    Map'(..)
    -- * Untagged Map Effect
    -- | If you don't require disambiguation of multiple map effects
    -- (i.e., you only have one map effect in your monadic context),
    -- it is recommended to always use the untagged map effect.
  , Map
  , clear
  , lookup
  , update
    -- * Convenience Functions
    -- | If you don't require disambiguation of multiple map effects
    -- (i.e., you only have one map effect in your monadic context),
    -- it is recommended to always use the untagged functions.
  , delete'
  , delete
  , exists'
  , exists
  , insert'
  , insert
  , modify'
  , modify
    -- * Tagging and Untagging
    -- | Conversion functions between the tagged and untagged map effect,
    -- usually used in combination with type applications, like:
    --
    -- @
    --     'tagMap'' \@\"newTag\" program
    --     'retagMap'' \@\"oldTag\" \@\"newTag\" program
    --     'untagMap'' \@\"erasedTag\" program
    -- @
    -- 
  , tagMap'
  , retagMap'
  , untagMap'
  ) where

-- base
import Data.Maybe     (isJust)
import Prelude hiding (lookup)

import Control.Effect.Machinery

-- | An effect that adds a mutable collection of key-value pairs to a given computation.
class Monad m => Map' tag k v m | tag m -> k v where
  -- | Deletes all key-value pairs from the map.
  clear' :: m ()
  -- | Searches for a value that corresponds to a given key.
  -- Returns 'Nothing' if the key cannot be found.
  lookup' :: k -> m (Maybe v)
  -- | Updates the value that corresponds to a given key.
  -- Passing 'Nothing' as the updated value removes the key-value pair from the map.
  update' :: k -> Maybe v -> m ()

makeTaggedEffect ''Map'

-- | Deletes a key and its corresponding value from the map.
delete' :: forall tag k v m. Map' tag k v m => k -> m ()
delete' k = update' @tag k Nothing
{-# INLINE delete' #-}

-- | The untagged version of 'delete''.
delete :: Map k v m => k -> m ()
delete = delete' @G
{-# INLINE delete #-}

-- | Checks if the map contains a given key.
exists' :: forall tag k v m. Map' tag k v m => k -> m Bool
exists' = fmap isJust . lookup' @tag
{-# INLINE exists' #-}

-- | The untagged version of 'exists''.
exists :: Map k v m => k -> m Bool
exists = exists' @G
{-# INLINE exists #-}

-- | Inserts a new key-value pair into the map. If the key is already present
-- in the map, the associated value is replaced with the new value.
insert' :: forall tag k v m. Map' tag k v m => k -> v -> m ()
insert' k = update' @tag k . Just
{-# INLINE insert' #-}

-- | The untagged version of 'insert''.
insert :: Map k v m => k -> v -> m ()
insert = insert' @G
{-# INLINE insert #-}

-- | Updates the value that corresponds to a given key.
-- If the key cannot be found, a corresponding default value is assumed.
modify' :: forall tag k v m. Map' tag k v m
        => v        -- ^ The default value that is assumed if the key is missing.
        -> (v -> v) -- ^ The function for updating the value. This function is
                    -- also applied to the default value if the key is missing.
        -> k        -- ^ The key whose corresponding value is updated.
        -> m ()     -- ^ The operation produces no value.
modify' fallback f k = do
  maybeVal <- lookup' @tag k
  case maybeVal of
    Just v  -> insert' @tag k (f v)
    Nothing -> insert' @tag k (f fallback)
{-# INLINE modify' #-}

-- | The untagged version of 'modify''.
modify :: Map k v m => v -> (v -> v) -> k -> m ()
modify = modify' @G
{-# INLINE modify #-}