Concurrency in Action

The previous example provided a nice introduction to FRP design with Juniper, however there is a way to use a standard library function other than foldP. In this section we will show that the blink example can be just as short as the standard Arduino example that uses delay. We will also abstract away the blink logic, so that we can re-use it for multiple output LEDs.

module Blink2
open(Io, Time)

/* In this example we have two LEDs blinking at different rates */

let boardLedA : uint16 = 13
let boardLedB : uint16 = 9

let mut timerStateA = Time:state
let mut timerStateB = Time:state

let mut ledStateA = low()
let mut ledStateB = low()

fun blink(interval, pin, inout timerState, inout ledState) =
    Time:every(interval, inout timerState) |>
    Signal:toggle(low(), high(), inout ledState) |>
    Io:digOut(pin)

fun loop() = {
    blink(1000, boardLedA, inout timerStateA, inout ledStateA)
    blink(700, boardLedB, inout timerStateB, inout ledStateB)
}

fun setup() = {
    Io:setPinMode(boardLedA, Io:output())
    Io:setPinMode(boardLedB, Io:output())
}

Thanks to the powerful type inference used by Juniper, our code is completely type safe without any type annotations. Here we introduce the pipe operator |> as well as the Signal:toggle function.

The pipe operator is very simple syntax sugar for piping an expression into another function. In general, e1 |> f(a, b, ..., y) is equivalent to inserting e1 as the last argument to f: f(a, b, ..., y, e1).

The Signal:toggle function is a standard library that switches between two values given by the first two arguments. If the current state passed to toggle is equal to the first argument, the state is set to the second argument and vice versa. The toggling behaviour only occurs when the input signal holds a value (in this case, the pipe operator is used to pass the signal returned by Time:every).

Note that there is no "real" concurrent processing happening here, what's actually going on is the board is executing a tight loop, calling loop repeatedly, and doing the processing for LED A, then for LED B. Most Arduino chips do not have multicore processors, so even using traditional Arduino programming does not introduce any real concurrency. Also note that since Arduino boards do not have enough space for an operating system, there is no context switching or multiple threads. The great benefit of Juniper is that all the gnarly timing details are hidden behind the scenes, and the use of higher order functions (functions that take in functions as arguments) makes code re-use easy.