{-# LANGUAGE GeneralizedNewtypeDeriving #-}{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}------------------------------------------------------------------------ |-- Module : FRP.Reactive.Future-- Copyright : (c) Conal Elliott 2007-2008-- License : GNU AGPLv3 (see COPYING)-- -- Maintainer : conal@conal.net-- Stability : experimental-- -- A simple formulation of functional /futures/, roughly as-- described at <http://en.wikipedia.org/wiki/Futures_and_promises>.-- -- A /future/ is a value with an associated time of /arrival/. Typically,-- neither the time nor the value can be known until the arrival time.-- -- Primitive futures can be things like /the value of the next key you-- press/, or /the value of LambdaPix stock at noon next Monday/.-- -- Composition is via standard type classes: 'Functor', 'Applicative',-- 'Monad', and 'Monoid'. Some comments on the 'Future' instances of-- these classes:-- -- * Monoid: 'mempty' is a future that never arrives (infinite time and-- undefined value), and @a `mappend` b@ is the earlier of @a@ and @b@,-- preferring @a@ when simultaneous.-- -- * 'Functor': apply a function to a future argument. The (future)-- result arrives simultaneously with the argument.-- -- * 'Applicative': 'pure' gives value arriving negative infinity.-- '(\<*\>)' applies a future function to a future argument, yielding a-- future result that arrives once /both/ function and argument have-- arrived (coinciding with the later of the two times).-- -- * 'Monad': 'return' is the same as 'pure' (as usual). @(>>=)@ cascades-- futures. 'join' resolves a future future value into a future value.-- -- Futures are parametric over /time/ as well as /value/ types. The time-- parameter can be any ordered type and is particularly useful with time-- types that have rich partial information structure, such as /improving-- values/.----------------------------------------------------------------------moduleFRP.Reactive.Future(-- * Time & futuresTime,ftime,FutureG(..),isNeverF,inFuture,inFuture2,futTime,futVal,future,withTimeF-- * Tests,batch)whereimportData.Monoid(Monoid(..))importData.Max-- import Data.AddBoundsimportFRP.Reactive.Internal.Future-- TestingimportTest.QuickCheckimportTest.QuickCheck.CheckersimportTest.QuickCheck.Classes{----------------------------------------------------------
Time and futures
----------------------------------------------------------}-- | Make a finite timeftime::t->Timetftime=Max-- FutureG representation in Internal.Futureinstance(Boundedt,Eqt,EqPropt,EqPropa)=>EqProp(FutureGta)whereu=-=v|isNeverFu&&isNeverFv=propertyTrueFuturea=-=Futureb=a=-=b-- I'd rather say:-- -- instance (Bounded t, EqProp t, EqProp a) => EqProp (FutureG t a) where-- Future a =-= Future b =-- (fst a =-= maxBound && fst b =-= maxBound) .|. a =-= b-- -- However, I don't know how to define disjunction on QuickCheck properties.-- | A future's timefutTime::FutureGta->TimetfutTime=fst.unFuture-- | A future's valuefutVal::FutureGta->afutVal=snd.unFuture-- | A future value with given time & valuefuture::t->a->FutureGtafutureta=Future(ftimet,a)-- | Access time of futurewithTimeF::FutureGta->FutureGt(Timet,a)withTimeF=inFuture$\(t,a)->(t,(t,a))-- withTimeF = inFuture duplicate (with Comonad)-- TODO: Eliminate this Monoid instance. Derive Monoid along with all the-- other classes. And don't use mempty and mappend for the operations-- below. For one thing, the current instance makes Future a monoid but-- unFuture not be a monoid morphism.instance(Ordt,Boundedt)=>Monoid(FutureGta)wheremempty=Future(maxBound,error"Future mempty: it'll never happen, buddy")-- Pick the earlier future.Future(s,a)`mappend`Future(t,b)=Future(s`min`t,ifs<=tthenaelseb)-- Consider the following simpler definition:-- -- fa@(Future (s,_)) `mappend` fb@(Future (t,_)) =-- if s <= t then fa else fb-- -- Nothing can be known about the resulting future until @s <= t@ is-- determined. In particular, we cannot know lower bounds for the time.-- In contrast, the actual 'mappend' definition can potentially yield-- useful partial information, such as lower bounds, about the future-- time, if the type parameter @t@ has rich partial information structure-- (non-flat).-- For some choices of @t@, there may be an efficient combination of 'min'-- and '(<=)', so the 'mappend' definition is sub-optimal. In particular,-- 'Improving' has 'minI'.-- -- A future known never to happen (by construction), i.e., infinite time.-- isNever :: FutureG t a -> Bool-- isNever = isMaxBound . futTime-- where-- isMaxBound (Max MaxBound) = True-- isMaxBound _ = False-- -- This function is an abstraction leak. Don't export it to library-- users.{----------------------------------------------------------
Tests
----------------------------------------------------------}-- Represents times at a given instant.newtypeTimeInfot=TimeInfo(Maybet)derivingEqPropinstanceBoundedt=>Bounded(TimeInfot)whereminBound=TimeInfo(JustminBound)maxBound=TimeInfoNothing-- A time at a given instant can be some unknown time in the futureunknownTimeInFuture::TimeInfoaunknownTimeInFuture=TimeInfoNothing-- or, a known time in the past. We're ignoring known future times for now.knownTimeInPast::a->TimeInfoaknownTimeInPast=TimeInfo.JustinstanceEqa=>Eq(TimeInfoa)whereTimeInfoNothing==TimeInfoNothing=error"Cannot tell if two unknown times in the future are equal"TimeInfo(Just_)==TimeInfoNothing=FalseTimeInfoNothing==TimeInfo(Just_)=FalseTimeInfo(Justa)==TimeInfo(Justb)=a==binstanceOrda=>Ord(TimeInfoa)where-- The minimum of two unknown times in the future is an unkown time in the-- future.TimeInfoNothing`min`TimeInfoNothing=unknownTimeInFutureTimeInfoNothing`min`b=ba`min`TimeInfoNothing=aTimeInfo(Justa)`min`TimeInfo(Justb)=(TimeInfo.Just)(a`min`b)TimeInfoNothing<=TimeInfoNothing=error"Cannot tell if one unknown time in the future is less than another."TimeInfoNothing<=TimeInfo(Just_)=FalseTimeInfo(Just_)<=TimeInfoNothing=TrueTimeInfo(Justa)<=TimeInfo(Justb)=a<=bbatch::TestBatchbatch=("FRP.Reactive.Future",concatMapunbatch[monoid(undefined::FutureGNumTT),functorMonoid(undefined::FutureGNumT(T,NumT))-- Checking the semantics here isn't necessary because-- the implementation is identical to them.---- Also, Functor, Applicative, and Monad don't require checking-- since they are automatically derived.---- , semanticMonoid' (undefined :: FutureG NumT T)-- , functor (undefined :: FutureG NumT (T,NumT,T))-- , semanticFunctor (undefined :: FutureG NumT ())-- , applicative (undefined :: FutureG NumT (NumT,T,NumT))-- , semanticApplicative (undefined :: FutureG NumT ())-- , monad (undefined :: FutureG NumT (NumT,T,NumT))-- , semanticMonad (undefined :: FutureG NumT ()),("specifics",[("laziness",propertylaziness)])])wherelaziness::BoundedT->T->Propertylazinessta=(uf`mappend`uf)`mappend`kf=-=kfwhereuf=unknownFuturekf=knownFutureknownFuture=future(knownTimeInPastt)aunknownFuture=futureunknownTimeInFuture(error"cannot retrieve value at unknown time at the future")-- Move to checkerstypeBoundedT=Int