packages feed

brush-stroking-0.1.0.0: src/BrushStroking/Unique.hs

{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE UndecidableInstances #-}

module BrushStroking.Unique
  ( MonadUnique(freshUnique)
  , Unique, unsafeUnique
  , uniqueText
  , UniqueSupply, newUniqueSupply
  , uniqueMapFromList
  )
  where

-- base
import Control.Arrow
  ( (&&&) )
import Data.Int
  ( Int64 )
import Data.Word
  ( Word32 )
import Foreign.Storable
  ( Storable )

-- containers
import Data.Map.Strict
  ( Map )
import qualified Data.Map.Strict as Map
  ( fromList )

-- deepseq
import Control.DeepSeq
  ( NFData )

-- generic-lens
import Data.Generics.Product.Typed
  ( HasType(typed) )

-- lens
import Control.Lens
  ( view )

-- mtl
import Control.Monad.Reader
  ( MonadReader(..) )

-- stm
import Control.Concurrent.STM
  ( STM )
import qualified Control.Concurrent.STM as STM

-- text
import Data.Text
  ( Text )
import qualified Data.Text as Text
  ( pack )

-- transformers
import Control.Monad.IO.Class
  ( MonadIO(..) )
import Control.Monad.Trans.Class
  ( lift )
import Control.Monad.Trans.Reader
  ( ReaderT )

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

newtype Unique = Unique { unique :: Int64 }
  deriving stock   Show
  deriving newtype ( Eq, Ord, Enum, Storable, NFData )

unsafeUnique :: Word32 -> Unique
unsafeUnique i = Unique ( -(fromIntegral i) - 1 )

uniqueText :: Unique -> Text
uniqueText ( Unique i )
  | i >= 0
  = "%" <> Text.pack ( show i )
  | otherwise
  = "§" <> Text.pack ( show $ -i - 1 )

newtype UniqueSupply = UniqueSupply { uniqueSupplyTVar :: STM.TVar Unique }

instance Show UniqueSupply where { show _ = "Unique supply" }

newUniqueSupply :: IO UniqueSupply
newUniqueSupply = UniqueSupply <$> STM.newTVarIO ( Unique 1 )

uniqueMapFromList :: HasType Unique a => [ a ] -> Map Unique a
uniqueMapFromList = Map.fromList . map ( view typed &&& id )

class Monad m => MonadUnique m where
  freshUnique :: m Unique

instance {-# OVERLAPPABLE #-} ( Monad m, MonadReader r m, HasType UniqueSupply r, MonadIO m ) => MonadUnique m where
  freshUnique = do
    UniqueSupply { uniqueSupplyTVar } <- view ( typed @UniqueSupply )
    liftIO $ STM.atomically $ STM.stateTVar uniqueSupplyTVar doSucc

instance MonadUnique ( ReaderT UniqueSupply STM ) where
  freshUnique = do
    UniqueSupply { uniqueSupplyTVar } <- ask
    lift $ STM.stateTVar uniqueSupplyTVar doSucc

doSucc :: Unique -> ( Unique, Unique )
doSucc uniq@( Unique !i ) = ( uniq, Unique ( succ i ) )