[Haskell-cafe] Stronger STM primitives needed? Or am I just doing
it wrong?
Ryan Ingram
ryani.spam at gmail.com
Tue Apr 22 17:48:54 EDT 2008
How can I implement the following operation efficiently in STM? Given
a TVar "now",
waitFor t0 = do
t <- readTVar now
if (t < t0) then retry else return ()
This naive implementation has the problem that the transaction gets
restarted every time "now" gets updated, even if the new value is
still less than t0.
One primitive that would be strong enough is this:
retryUntil :: TVar a -> (a -> Bool) -> STM ()
although it would still do some computation every time "now" changed.
The thought I originally had was to register the "waitFor" time with
some helper which kept track of the current time and fired off a
notice when it was ready. But the problem with that is that "retry"
undoes all the work of setting that up; the goal is still to block,
but I want a stronger blocking primitive.
Does anyone have any ideas?
-- ryan
Here's the background: I'm following along with Conal's excellent FRP
paper at http://conal.net/papers/simply-reactive/
In his implementation of futures, Conal uses a race primitive which
spawns two threads each computing a value that (if successful) is
guaranteed to agree. I was (and still am) sure that an implementation
using STM could avoid needing to race, with careful use of orElse &
retry.
Here's the datatype I'm using for Future:
> -- semantic type for future
> -- t = type of Time, an instance of Bounded. a = value type
> type F t a = (t, a)
> force :: Future t a -> F t a
> data Future t a = Fut
> { waitFor :: t -> STM ()
> , value :: STM (t, a)
> }
> firstFuture :: Future t a -> Future t a -> Future t a
> -- with semantics:
> -- force (firstFuture f1 f2) = (min t1 t2, if t1 <= t2 then v1 else v2)
> -- where
> -- (t1, v1) = force f1
> -- (t2, v2) = force f2
Each future lives in some universe; imagine you have the following:
] type Universe t -- abstract
] P :: Bounded t => Universe t -- universe of pure values
] R :: Universe Time -- universe of the real world
] univ :: Future t a -> Universe t
] now :: Universe t -> STM t
] -- now P = return maxBound
] -- now R = current time
The main thrust of this is that each universe has its own idea of what
time it is; but when combining futures, we get a new universe which
tracks the later of the times in the two universes. The problem is
combining two futures:
Lets say we have:
anyKey :: Future Time () -- fires when the user first presses a key
thousand :: Num t => Future t () -- fires at tick 1000
thousand = exactly 1000 ()
and we are evaluating
force (firstFuture anyKey thousand)
Now, "clock" is going to fire at tick 1000; we know this because it
lives in the pure universe and its value is always available. So
firstFuture can get the following:
x1 <- maybeSTM (value anyKey)
-- x1 = Nothing
x2 <- maybeSTM (value clock)
-- x2 = Just (1000, ())
...
-- synchronize P with R at tick 1000
waitFor anyKey 1000 -- if nothing has changed before tick 1000
return (1000, ())
-- converts a possibly-retry-calling STM into one that never fails
maybeSTM m = fmap Just m `orElse` return Nothing
So if we use the naive implementation for "waitFor" in terms of "now",
the whole transaction will get re-evaluated every tick. I only want
it to get re-evaluated if the user presses a key (changing some TVar
evaluated in "value anyKey"), or tick 1000 passes, whichever comes
first. Is there a way to do this? Is my choice of "waitFor" as the
basic "universe synchronization" operation too weak?
More information about the Haskell-Cafe
mailing list