{-# OPTIONS_GHC -fglasgow-exts #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Control.Arrow.Transformer.Stream
-- 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)
--
-- Arrow transformer lifting an arrow to streams.

module Control.Arrow.Transformer.Stream(
		StreamArrow,
		runStream,
		StreamMap,
		StreamMapST, runStreamST,
		ArrowAddStream(..),
	) where

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

import Data.Stream (Stream(..))
import qualified Data.Stream as Stream

import Control.Applicative
import Control.Arrow hiding (pure)
import Control.Monad.ST
import Data.Monoid

-- | Arrows between streams.
--
-- /Note/: 'lift' is only a functor if '***' in the underlying arrow is.

newtype StreamArrow a b c = Str (a (Stream b) (Stream c))

instance Arrow a => Arrow (StreamArrow a) where
	arr f = Str (arr (fmap f))
	Str f >>> Str g = Str (f >>> g)
	first (Str f) =
		Str (arr Stream.unzip >>> first f >>> arr (uncurry Stream.zip))

genmap :: Arrow a => a b c -> a (Stream b) (Stream c)
genmap f = arr (\xs -> (Stream.head xs, Stream.tail xs)) >>>
		f *** genmap f >>> arr (uncurry (Stream.Cons))

-- Caution: genmap is only a functor if *** for the base arrow is.
-- (For Kleisli arrows, that would mean a commutative monad.)
-- The same goes for the equivalent lift: it can be used to lift arrows,
-- but won't preserve composition unless *** does.

instance Arrow a => ArrowTransformer (StreamArrow) a where
	lift f = Str (genmap f)

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

instance ArrowZero a => ArrowZero (StreamArrow a) where
	zeroArrow = lift zeroArrow

instance ArrowState s a => ArrowState s (StreamArrow a) where
	fetch = lift fetch
	store = lift store

instance ArrowWriter w a => ArrowWriter w (StreamArrow a) where
	write = lift write
	newWriter (Str f) = Str (newWriter f >>> arr strength)
		where	strength :: Functor w' => (w' a',b) -> w' (a',b)
			strength (v, y) = fmap (\x -> (x, y)) v

-- liftings of standard classes

instance Arrow a => ArrowChoice (StreamArrow a) where
	left (Str f) = Str ((arr getLeft >>> f) &&& arr id >>> arr replace)
		where	getLeft (Cons (Left x) xs) = Cons x (getLeft xs)
			getLeft (Cons (Right _) xs) = getLeft xs
			replace (~(Cons x xs), Cons (Left _) ys) =
				Cons (Left x) (replace (xs, ys))
			replace (xs, Cons (Right y) ys) =
				Cons (Right y) (replace (xs, ys))

instance ArrowLoop a => ArrowLoop (StreamArrow a) where
	loop (Str f) =
		Str (loop (arr (uncurry Stream.zip) >>> f >>> arr Stream.unzip))

instance ArrowPlus a => ArrowPlus (StreamArrow a) where
	Str f <+> Str g = Str (f <+> g)

-- I don't know of any other useful promotions.
-- (elimWriter can be promoted, but doesn't seem useful.)

-- Circuits

instance ArrowLoop a => ArrowCircuit (StreamArrow a) where
	delay x = Str (arr (Cons x))

-- Other instances

instance Arrow a => Functor (StreamArrow a b) where
	fmap f g = g >>> arr f

instance Arrow a => Applicative (StreamArrow a b) where
	pure x = arr (const x)
	f <*> g = f &&& g >>> arr (uncurry id)

instance ArrowPlus a => Alternative (StreamArrow a b) where
	empty = zeroArrow
	f <|> g = f <+> g

instance ArrowPlus a => Monoid (StreamArrow a b c) where
	mempty = zeroArrow
	mappend f g = f <+> g

-- | Run a stream processor on a stream of inputs, obtaining a stream
-- of outputs.
--
-- Typical usage in arrow notation:
--
-- >	proc p -> do
-- >		...
-- >		ys <- (|runStream (\x -> ...)|) xs
--
-- Here @xs@ refers to the input stream and @x@ to individual
-- elements of that stream.  @ys@ is bound to the output stream.

runStream :: ArrowLoop a => StreamArrow a (e,b) c -> a (e,Stream b) (Stream c)
runStream (Str f) = arr (\(e, xs) -> fmap (\x -> (e, x)) xs) >>> f

instance ArrowLoop a => ArrowAddStream (StreamArrow a) a where
	liftStream = lift
	elimStream = runStream

-- | Mappings of streams
type StreamMap = StreamArrow (->)

-- | In-place state updates.
--
-- /Note/: this is an arrow type, and 'lift' can be used to promote arrows
-- from @'Kleisli' ('ST' s)@: the resulting arrow updates the state for
-- each stream element in turn, and as long as the final state in not
-- required all is well.  However, 'lift' does not preserve composition,
-- because this monad isn't commutative.  In particular, a composition
-- of 'lift's of state transformers will not work, as the second will
-- require the final state of the first.

type StreamMapST s = StreamArrow (Kleisli (ST s))

-- | Encapsulate a local state.

runStreamST :: (forall s. StreamMapST s e c) -> StreamMap e c
runStreamST cf = Str $ \ input -> runST (let Str (Kleisli f) = cf in f input)