-- a fork of Sebastiaan Visser's BSD3 fclabels

{-# LANGUAGE TypeOperators, TypeSynonymInstances, TemplateHaskell #-}

module Air.Data.Record.SimpleLabel
  (
  -- * Getter, setter and modifier types.
    Getter
  , Setter
  , Modifier

  -- * Label type.
  , Point
  , (:->) (Label)
  , label
  , get, set, mod
  , getM, setM, modM
  , (=:)
  )
where

import Prelude hiding ((.), id, mod)
import Control.Applicative
import Control.Category
import Control.Monad.State hiding (get)


type Getter   s x   = s -> x
type Setter   s x   = x -> s -> s
type Modifier s x = (x -> x) -> s -> s

data Point s x = Point
  { _get :: Getter s x
  , _set :: Setter s x
  }

_mod :: Point s x -> (x -> x) -> s -> s
_mod l f a = _set l (f (_get l a)) a

newtype (s :-> x) = Label { unLabel :: Point s x }


-- Create a label out of a getter and setter.

label :: Getter s x -> Setter s x -> s :-> x
label g s = Label (Point g s)

-- | Get the getter function from a label.

get :: (s :-> x) -> s -> x
get = _get . unLabel

-- | Get the setter function from a label.

set :: (s :-> x) -> x -> s -> s
set = _set . unLabel

-- | Get the modifier function from a label.

mod :: (s :-> x) -> (x -> x) -> s -> s
mod = _mod . unLabel

instance Category (:->) where
  id = Label (Point id const)
  (Label a) . (Label b) = Label (Point (_get a . _get b) (_mod b . _set a))

-- | Get a value out of state pointed to by the specified label.

getM :: MonadState s m => s :-> b -> m b
getM = gets . get

-- | Set a value somewhere in state pointed to by the specified label.

setM :: MonadState s m => s :-> b -> b -> m ()
setM l = modify . set l

-- | Alias for `setM' that reads like an assignment.

infixr 7 =:
(=:) :: MonadState s m => s :-> b -> b -> m ()
(=:) = setM

-- | Modify a value with a function somewhere in state pointed to by the
-- specified label.

modM :: MonadState s m => s :-> b -> (b -> b) -> m ()
modM l = modify . mod l