{-# OPTIONS_GHC -fglasgow-exts #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Control.Arrow.Transformer.State
-- Copyright   :  (c) Ross Paterson 2003
-- License     :  BSD-style (see the LICENSE file in the distribution)
--
-- Maintainer  :  ross@soi.city.ac.uk
-- Stability   :  experimental
-- Portability :  non-portable (multi-parameter type classes)
--
-- An arrow transformer that adds a modifiable state,
-- based of section 9 of /Generalising Monads to Arrows/, by John Hughes,
-- /Science of Computer Programming/ 37:67-111, May 2000.

module Control.Arrow.Transformer.State(
		StateArrow,
		runState,
		ArrowAddState(..),
	) where

import Control.Arrow
import Control.Arrow.Internals
import Control.Arrow.Operations
import Control.Arrow.Transformer

-- | An arrow type that augments an existing arrow with a modifiable
-- state.  The 'ArrowState' class contains the operations on this state.
newtype StateArrow s a b c = ST (a (b, s) (c, s))

swapsnd :: ((a, b), c) -> ((a, c), b)
swapsnd ~(~(x, y), z) = ((x, z), y)

instance Arrow a => Arrow (StateArrow s a) where
	arr f = ST (arr (\(x, s) -> (f x, s)))
	ST f >>> ST g = ST (f >>> g)
	first (ST f) = ST (arr swapsnd >>> first f >>> arr swapsnd)

instance Arrow a => ArrowTransformer (StateArrow s) a where
	lift f = ST (first f)

-- | Encapsulation of a state-using computation, exposing the initial
-- and final states.
--
-- Typical usage in arrow notation:
--
-- >	proc p -> do
-- >		...
-- >		(result, final_state) <- (|runState cmd|) init_state

runState :: Arrow a => StateArrow s a e b -> a (e,s) (b,s)
runState (ST f) = f

-- operations

instance Arrow a => ArrowState s (StateArrow s a) where
	fetch = ST (arr (\(_, s) -> (s, s)))
	store = ST (arr (\(s, _) -> ((), s)))

instance Arrow a => ArrowAddState s (StateArrow s a) a where
	liftState = lift
	elimState = runState

-- The following promotions follow directly from the arrow transformer.

instance ArrowZero a => ArrowZero (StateArrow s a) where
	zeroArrow = ST zeroArrow

instance ArrowCircuit a => ArrowCircuit (StateArrow s a) where
	delay x = lift (delay x)

instance ArrowError ex a => ArrowError ex (StateArrow s a) where
	raise = lift raise
	handle (ST f) (ST h) = ST (handle f (arr swapsnd >>> h))
	tryInUnless (ST f) (ST s) (ST h) =
		ST (tryInUnless f (arr new_state >>> s) (arr swapsnd >>> h))
		where	new_state ((b,_),(c,s')) = ((b,c),s')
	newError (ST f) = ST (newError f &&& arr snd >>> arr h)
		where	h (Left ex, s) = (Left ex, s)
			h (Right (c, s'), _) = (Right c, s')

-- Note that in each case the error handler gets the original state.

instance ArrowReader r a => ArrowReader r (StateArrow s a) where
	readState = lift readState
	newReader (ST f) = ST (arr swapsnd >>> newReader f)

instance ArrowWriter w a => ArrowWriter w (StateArrow s a) where
	write = lift write
	newWriter (ST f) = ST (newWriter f >>> arr swapsnd)

-- liftings of standard classes

instance ArrowChoice a => ArrowChoice (StateArrow s a) where
	left (ST f) = ST (arr distr >>> left f >>> arr undistr)
		where	distr (Left y, s) = Left (y, s)
			distr (Right z, s) = Right (z, s)
			undistr (Left (y, s)) = (Left y, s)
			undistr (Right (z, s)) = (Right z, s)

instance ArrowApply a => ArrowApply (StateArrow s a) where
	app = ST (arr (\((ST f, x), s) -> (f, (x, s))) >>> app)

instance ArrowLoop a => ArrowLoop (StateArrow s a) where
	loop (ST f) = ST (loop (arr swapsnd >>> f >>> arr swapsnd))

instance ArrowPlus a => ArrowPlus (StateArrow s a) where
	ST f <+> ST g = ST (f <+> g)

-- promotions

instance ArrowAddReader r a a' =>
		ArrowAddReader r (StateArrow s a) (StateArrow s a') where
	liftReader (ST f) = ST (liftReader f)
	elimReader (ST f) = ST (arr swapsnd >>> elimReader f)

instance ArrowAddWriter w a a' =>
		ArrowAddWriter w (StateArrow s a) (StateArrow s a') where
	liftWriter (ST f) = ST (liftWriter f)
	elimWriter (ST f) = ST (elimWriter f >>> arr swapsnd)

instance ArrowAddError ex a a' =>
		ArrowAddError ex (StateArrow s a) (StateArrow s a') where
	liftError (ST f) = ST (liftError f)
	elimError (ST f) (ST h) = ST (elimError f (arr swapsnd >>> h))