Freckle


Part 1: Understanding Functional Reactive Programming

Functional Reactive Programming (or short FRP), is often mired in abstract languge and theoretical functional programming lingo. In this tutorial we will attempt to keep it simple and easy to follow.

To begin with lets define FRP. In order for something to be classified as frp it must be:

  • Composable and Declarative (i.e. functional)
  • Reactive
  • Sampling Resolution Insensitive

The first two requirements are fairly easy to understand, i.e. of course it must be functional and be able to react to events occuring in the system. However the 3. requirement Sampling Resolution Insensitive, requires that we understand what sampling resolution is.

In short the sampling resolution is analogous to the the span of time between each sample, in video games this is often refered to as the tick rate.

However let us see how this problem plays out in an actual application. Imagine we have the following program

1: 
2: 
while true do
    update ()

This program updates its state and nothing more, the sampling resolution of this program depends entirely on computer running it and how costly the function update is. If we assume that it always takes 16 ms to execute update then we get a sampling resolution at

1: 
1000 / 16 ms ~ 60 ticks per seconds

Meaning that update is called about 60 times per seconds.

However as is obvious there is no guarantees given that update will take exactly 16 ms to execute. Which might be a problem if an application needs to update at precise interval, old programs have sometimes ignored, and thus when executed on modern computers they run extremly fast or have completely stopped functioning properly.

The a naive solution to account for a fixed sampling rate, would be to delay after the update. e.g.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let desiredDelay = time 16

while true do
    let startTime = now ()
    update ()
    let diffTime = now () - startTime
    wait (desiredDelay - diffTime)

Initially this provides us with what we required, the program operates consistently at a fixed sampling rate. However as you can probably already observe, if for any reason at any point during the life time of the program, update takes longer than expected. Meaning that diffTime > desiredDelay i.e. requring us to wait negative, then the program will become out of sync forever.

If diffTime > desiredDelay happens consistenly on average, then there is nothing we can do, from a development perspective that can be done. This is because the resolution supported by the system is too low to support the resolution required. In the same way that if we have a picture with a high resolution, showing it on a small screen doesn't show any more details. The resolution required and the resolution supported must always follow each other, however they need not necessarily perfectly match, which is why we must design our programs to accomondate this.

Otherwise If diffTime > desiredDelay happens only on rare occasions, then on those occasions we will forever delay all feature updates. e.g. Imagine a program simulating a car but with the expectation that an updateCar function is called 10 times a second, then skipping a couple of calculations can have a huge impact on the result.

To solve the second of the two problems let us propose the another naive solution

1: 
2: 
3: 
4: 
5: 
6: 
7: 
while true do
    let startTime = now ()
    if lastDiff < 0 then
        update ()
    update ()
    let diffTime = now () - startTime
    wait (desiredDelay - diffTime)

This solution solves the problem by calling update an extra time in the event it becomes out of sync. So job done? Not quite... because depending on how long a single execution takes it might still cause update to become out of sync.

Imagine that update was for a graphical application and looked something like this:

1: 
2: 
3: 
let update () =
    let state = updateState() //Fairly fast state update
    render state              //Expensive graphic render function

As you might imagine rendering two times in a row doesn't change anything from an application perspective, however it is a huge drain on the resources of the cpu. So what we in reality want is something that allows us to define that if we miss an updateState then we guarantee that we calculate it but if we miss a render then will just be skipped.

Before we move onto how frp would handle these problems, let's highlight one thing that has been overlooked so far.

Remember the Insensitive in Sampling Resolution Insensitive, thus far the proposed solutions have completely overlooked this aspect. All we have achieved is uniform(kinda..) sampling however consider what would happen if we changed the desiredDelay to half its current value. Essentially this would mean update is called twice as many times, or in other words the application is Sampling Resolution Sensitive.

On reflection if we consider all the prior problems they have all been the victim of some form of Sampling Resolution Sensitivity. This is why Sampling Resolution Insensitivity is and must be a property of FRP.

So far the problems we have shown are

  1. Unpredictable Sampling
  2. Single samplings causing out of sync
  3. Redudant sampling
  4. The root to all of these being Sampling Resolution Sensitivity

Part 2: Using Freckle

Now that we understand the problem of Sampling Resolution Sensitivity, let's see how code using freckle would look.

Lets assume we are building a simple count app, that will count and show the result in the console.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
//Simple count function
let updateState s = s + 1

//Wrapping a simple print in async, might seem strange if you are used to C#.
//However if you tried haskell's IO syntax this should feel right at home
let render at state = 
    async {
        //is the time when render was called
        return printfn "%A: %d" (Time.toDateTime at).TimeOfDay state
    } 

//The app function is our declarative description of what our application does
let app (state : int) : Sample<Async<int>>=
    //sampleAsync is the same as Sample<Async<_>>
    sampleAsync {
        let! state' = 
            Feed.pulse 1 //We generate a pulse at one tick per second
            |> Sample.bind (Feed.foldPast (fun s _ -> updateState s) state) //Fold over all pulses generated in this sample
            |> SampleAsync.ofSample //Convert our Sample<int> to Sample<Async<int>>
        
        //the Upto suffix inform the sampler that if we miss a pulse then we don't care
        do! Feed.pulseUpto 1
            |> Sample.map (Feed.map (fun at -> render at state')) //Change the pulses to the render async
            |> Sample.bind Feed.plan_ //plan converts a feed of async into an async of feed 
                                      //plan_ is the same but it discards the resulting feed
        return state'
    }

To set a bounded resolution we simply append a delay 100ms ~ 10 ticks per second

1: 
2: 
3: 
let boundResolutionApp state = 
    app state
    |> SampleAsync.doAsync (Async.Sleep 100) 

With this all that should be left is simply to begin sampling

1: 
2: 
3: 
//the sampler needs a clock to meassure time, the app function and a start state in this case zero
Sample.sampleForever Clock.systemUtc boundResolutionApp 0
|> Async.RunSynchronously

Run 1

1: 
2: 
3: 
4: 
5: 
> 00:00:01: 1
> 00:00:02: 2
> 00:00:03: 3
> 00:00:04: 4
....

So far so good we see that even though we sample 10 times more times than we need our app remains consistent.

Question: what happens if the resolution is below our needs? Let's see what occurs when we set the delay to 2 seconds

1: 
2: 
3: 
let boundResolutionApp state = 
    app state
    |> SampleAsync.doAsync (Async.Sleep 2000)

Run 2

1: 
2: 
3: 
4: 
5: 
> 00:00:02: 2
> 00:00:04: 4
> 00:00:06: 6
> 00:00:08: 8
....

As we can see the prints (the call to our render function) only happens every other second. However our counting remains correct. You could say that our application changed behavior duo to changing of the resolution, and thus is not Sampling Resolution Insensitive. However this is from the app's perspective completely expected and counted for behavior, as it explicitly pulseUpto for the printout. In truth the app only cares about getting a correct count, printout is a secondary concern and it is only interested in it occuring at some point. Thus the app maintains its property of being Insensitive

For a fun exercise try and run the code yourself!, try and see what happens if the resolution is unbounded.

val update : unit -> unit

Full name: Tutorial.update
Multiple items
union case Time.Time: unit -> Time

--------------------
type Time =
  | Time of unit
  static member ( - ) : 'a * 'b -> Time

Full name: Tutorial.Time
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
val now : unit -> Time

Full name: Tutorial.now
val time : 'a -> Time

Full name: Tutorial.time
val wait : (Time -> unit)

Full name: Tutorial.wait
val updateState : unit -> unit

Full name: Tutorial.updateState
val render : unit -> unit

Full name: Tutorial.render
namespace Freckle
val desiredDelay : Time

Full name: Tutorial.desiredDelay
val startTime : Time
val diffTime : Time
val state : unit
val updateState : s:int -> int

Full name: Tutorial.updateState
val s : int
val render : at:Time -> state:int -> Async<unit>

Full name: Tutorial.render
val at : Time
val state : int
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Multiple items
union case Time.Time: unit -> Time

--------------------
type Time =
  {Ticks: Ticks;}
  override ToString : unit -> string
  static member between : a:Time -> b:Time -> Time
  static member maxValue : Time
  static member origin : Time
  static member ofDateTime : d:DateTime -> Time
  static member ofDays : days:int32 -> Time
  static member ofHours : hour:int32 -> Time
  static member ofMicroseconds : microSec:int32 -> Time
  static member ofMilliseconds : ms:int32 -> Time
  static member ofMinutes : min:int32 -> Time
  static member ofSeconds : sec:int32 -> Time
  static member ( + ) : t1:Time * t2:Time -> Time
  static member ( - ) : t1:Time * t2:Time -> Time
  static member ticks : t:Time -> Ticks
  static member time : t:Ticks -> Time
  static member toDateTime : t:Time -> DateTime

Full name: Freckle.Time
static member Time.toDateTime : t:Time -> System.DateTime
val app : state:int -> Sample<Async<int>>

Full name: Tutorial.app
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
module Sample

from Freckle

--------------------
type Sample<'a> = Period -> 'a

Full name: Freckle.Sample<_>
Multiple items
module Async

from Freckle

--------------------
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
val sampleAsync : SampleAsync.ComputationalExpression.Builder

Full name: Freckle.ComputationExpression.sampleAsync
val state' : int
Multiple items
union case Feed.Feed: LazyList<Time * 'e> -> Feed<'e>

--------------------
module Feed

from Freckle

--------------------
type Feed<'e> =
  | Feed of LazyList<Time * 'e>
  override ToString : unit -> string

Full name: Freckle.Feed<_>
val pulse : pulsePerSecond:int -> Sample<Feed<Time>>

Full name: Freckle.Feed.pulse
val bind : f:('a -> Sample<'b>) -> m:Sample<'a> -> Sample<'b>

Full name: Freckle.Sample.bind
val foldPast : f:('s -> 'a -> 's) -> state:'s -> fr:Feed<'a> -> Sample<'s>

Full name: Freckle.Feed.foldPast
module SampleAsync

from Freckle
val ofSample : x:Sample<'a> -> Sample<Async<'a>>

Full name: Freckle.SampleAsync.ofSample
val pulseUpto : pulsePerSecond:int -> Sample<Feed<Time>>

Full name: Freckle.Feed.pulseUpto
val map : f:('a -> 'b) -> sample:Sample<'a> -> Sample<'b>

Full name: Freckle.Sample.map
val map : f:('a -> 'b) -> fr:Feed<'a> -> Feed<'b>

Full name: Freckle.Feed.map
val plan_ : fr:Feed<Async<'a>> -> Sample<Async<unit>>

Full name: Freckle.Feed.plan_
val boundResolutionApp : state:int -> Sample<Async<int>>

Full name: Tutorial.boundResolutionApp
val doAsync : das:Async<'a> -> sa:Sample<Async<'b>> -> Sample<Async<'b>>

Full name: Freckle.SampleAsync.doAsync
static member Async.Sleep : millisecondsDueTime:int -> Async<unit>
val sampleForever : clock:Clock -> sampler:('s -> Sample<Async<'s>>) -> state:'s -> Async<'a>

Full name: Freckle.Sample.sampleForever
Multiple items
union case Clock.Clock: Async<Time> -> Clock

--------------------
module Clock

from Freckle.Clock

--------------------
module Clock

from Freckle

--------------------
type Clock = | Clock of Async<Time>

Full name: Freckle.Clock.Types.Clock
val systemUtc : Clock

Full name: Freckle.Clock.Clock.systemUtc
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:System.Threading.CancellationToken -> 'T
Fork me on GitHub