In addition to supporting Stepper a o
functions, it's possible
to write "fueled" computations. A Fueled a
runs some number of steps
and either isOutOfGas
or yields a value of type a
.
For those familiar with the functor/applicative/monad family, Fueled
is a monad, and that is the best way to interact with it.
There are two ways to run programs: all the way, via
runToCompletion
, or by passing the stepper
to Trampoline.init
.
Fueled
computationsThe repository on GitHub has more examples. The basic idiom of Fueled computations can be seen in the following function:
countDown : Int -> Fueled Int
countDown n =
if n <= 0
then return 0
else call countDown (n-1)
A few notes:
Fueled
functions should never take Fueled
arguments. Use andThen
.return
.call
.The countDown
example is pretty silly---it always returns 0. Boring. Here's a marginally less boring example:
factorial : Int -> Fueled Int
factorial n =
if n <= 0
then return 1
else call factorial (n-1) |> andThen (\res ->
return (res * n))
We can add a note:
andThen
for sequencing.Here are three diverging functions, in reverse order of goodness:
bestDiverge : Int -> Fueled Int
bestDiverge n = call bestDiverge (n+1)
Simple, delightful. Like Charlie on the MTA, it never returns---but it doesn't blow up the stack.
betterDiverge : Int -> Fueled Int
betterDiverge n = burn <| lazy (\() -> betterDiverge (n+1))
The call
function amounts to exactly this idiom.
recoveringMonadAddictDiverge : Int -> Fueled Int
recoveringMonadAddictDiverge n = burn <|
(return (n+1) |> andThen diverge)
Using return
into andThen
is a pretty weird thing to do, but doing
it here has the same effect as using lazy
.
badDiverge : Int -> Fueled Int
badDiverge n = burn (badDiverge (n+1))
This function will blow up your stack---it won't actually suspend properly! Elm uses eager evaluation, so it has to compute badDiverge (n+1)
before calling burn
, which means computing badDiverge (n+2)
, and so on.
Internal.Fueled a
A Fueled a
is a suspendable computation that will either diverge
or eventually yield a value of type a
. The Fueled
type is abstract
to avoid shenanigans.
stepper : Trampoline.Stepper (Fueled a) a
Fueled computations have a stepper that can be used with
Trampoline.init
. To run a fueled computation, you might write:
main = Browser.element { init = Trampoline.init myInit Trampoline.Fueled.stepper, ... }
somewhere else, you can then run Trampoline.setInput
myFueledComputation AndGo
to set the fueled computation to run and
start stepping.
runToCompletion : Fueled a -> a
Fueled computations can also be simply run... at the risk of freezing the UI thread or blocking messages. Use this function carefully.
map : (a -> b) -> Fueled a -> Fueled b
Post-processing the results of a Fueled
computation.
return : a -> Fueled a
A fueled computation that returns immediately.
runToCompletion (return x) == x
ap : Fueled (a -> b) -> Fueled a -> Fueled b
Combining two Fueled
computations applicatively.
andThen : (a -> Fueled b) -> Fueled a -> Fueled b
Meant to be used with (|>)
, as in:
f |> Trampoline.Fueled.andThen (\x -> ...)
To bind the result of f
as x
.
call : (a -> Fueled b) -> a -> Fueled b
Makes a suspendable call to a Fueled
computation. All recursive
calls should be written:
f n = ... call f (n-1) ...
burn : Fueled a -> Fueled a
Uses up a bit of fuel.
lazy : (() -> Fueled a) -> Fueled a
Delays a Fueled computation.