LocSta is discontinued. Before starting to work with this library or thinking about forking it, please have a look at Vide, which is a successor of LocSta.
LocSta is authored by @SchlenkR. Feel free to leave a message.
LocSta is about
Composing 'state-aware' functions as if they were just normal functions.
What is state(ful) - from a programmers perspective?
- State is a value that changes over time, so it mutates, and without mutation, it's not called state.
- Usually, (local) state is represented by an instance of an object or a closure that captures a mutable value.
- State must then have an 'identity', which is represented by a persistent location in memory - a pointer.
- In order to use state from a computation, it means: It has to be created up-front, and it can be used afterwards by it's pointer.
- The separation of instanciation and usage makes composition hard, or at least uncomfortable (besides other thinks that make compotion hard or uncomfortable).
What is stateless?
- It's the opposite of stateful, which means:
- There is no mutation involved.
- No need for objects, state capturing closures or pointers - there are just functions.
- There is no persistence from one evaluation of a stateless computation to another.
- No separation of instanciation and usage, which is comfortable to write: A function can be used ad-hoc (or in-place) where it should be used - there is no de-localization in code between state instanciation and usage.
LocSta aims to providing coding comfort in a way that stateful computations can be treated and composed as if they were stateless functions.
While this might sound like dealing with impure or internally mutable functions, it is based on a pure function approach. The focus lies on dealing with sequences of values.
Even though many concepts overlap with seq
, there are significant differences in behaviour and usage, as well as in the fundamental ideas.
// There's currently no NuGet package available, so reference the dll directly.
#r "./src/LocSta/bin/Debug/netstandard2.0/LocSta.dll"
open LocSta
open LocSta.Lib.Gen
Count 2 values (use "count" as a stateful function)
- While
count
seems to be a pure function (there's no object instance or pointer to an object), it is stateful per se. - How does it work: The "Local State" CE evaluates the
count
functions, collects their state, and applies that state to thecount
functions again on subsequent evaluations.
loop {
let! v1 = count 0 1 // count from 0, increment by 1
let! v2 = count 100 5 // count from 100, increment by 5
yield v1 + v2
}
|> Gen.toListn 4
// [100; 106; 112; 118]
"Pairwise" sequence processing
- The pairwise characteristics seen in the example above can also be applied to sequences.
- Using the usual
seq
CE, the result woule be a cartesian product of sequence 1 and sequence 2
loop {
let! v1 = [ "a"; "b"; "c"; "d" ] |> Gen.ofList
let! v2 = [ 1 ; 2 ; 3 ; 4 ] |> Gen.ofList
yield v1,v2
}
|> Gen.toList
// [("a", 1); ("b", 2); ("c", 3); ("d", 4)]
Controlling
It is possible to explicitly control the workflow by emitting Stop
, Skip
and others:
loop {
let! v = count 0 1 // this would yield and never stop
if v = 5 then
return Loop.Skip // we don't want '5' to be part of the result
elif v = 10 then
return Loop.Stop // 'break' after 10 elements are yielded
else
yield v
}
|> Gen.toList
// [0; 1; 2; 3; 4; (* no 5 in here *) 6; 7; 8; 9]
Writing stateful functions
- Until now, we only used stateful functions. But what if we want to write functions, and maintain a state?
- Here's an example: Accumulate counted values, so that in each evaluation cycle, a list with all the counted values since beginning is yielded.
- Instead of the
loop
builder, it uses thefeed
builder.
feed {
let! state = Init [] // Place 'Init' on top of the computation and
// give it a seed value (here: an empty list).
// In the first evaluation, the seed value
// will be returned, but in subsequent evaluations,
// the 'newState' value will be returned.
let! v = count 0 1
let accumulatedValues = v :: state
let output = accumulatedValues |> List.rev
let newState = accumulatedValues
yield output, newState // yield a tuple of the actual return value
// and a state value.
}
|> Gen.toListn 4
// [
// [0]
// [0; 1]
// [0; 1; 2]
// [0; 1; 2; 3]
// ]
Tests
Please have a look at the base tests for getting an impression of how the library works and maybe what it could be good for.
Previous versions
https://github.com/fsprojects/LocSta/tree/2360afbc45f646338e725b8059943dff3d41c5af
F# Applied Challenge
The concept was also part of the F# Applied Challenge in 2019. Have a look ar the contribution explanation to find out more.
This library is still experimental and volatile.