Algebraic Effects
I propose we ...
Add an Algebraic Effects system.
This is not a precise definition, just a sketch of what it might look like...
Define effects with an input type and an output type using a new effect keyword:
effect ConsoleRead of unit -> string
effect ConsoleWrite of string -> unit
Functions can perform effects with a new perform keyword:
let greet () =
perform (ConsoleWrite "What is your name?")
let name = perform (ConsoleRead ())
perform (ConsoleWrite $"Hello %s{name}")
Functions are annotated with the set of effects they might perform during execution:
val greet : unit ->{ConsoleRead|ConsoleWrite} unit
And a normal function is equivalent to the empty set:
// String.length
val length : string -> int
val length : string ->{} int
Effect types propagate across function calls automatically:
let greetN (n : int) =
for _ = 1 to n do
greet ()
val greetN : int ->{ConsoleRead|ConsoleWrite} unit
And the effects of a function is the union of what it calls:
let read () =
perform (ConsoleRead ())
let write (x : string) =
perform (ConsoleWrite x)
let readAndWrite () =
write (read ())
val read : unit ->{ConsoleRead} string
val write : string ->{ConsoleWrite} unit
val readAndWrite : unit ->{ConsoleRead|ConsoleWrite} unit
Effects can be given a concrete handler with a new handle keyword, thus removing the effect type from the annotation:
open System
[<EntryPoint>]
let main _ =
handle
greet ()
with
| (ConsoleRead ()) k ->
let v = Console.ReadLine()
continue k v
| (ConsoleWrite v) k ->
Console.WriteLine(v)
continue k ()
0
The compiler guarantees that the entry point has no effects. In other words, all effectful computations must be a concrete handler.
Different handlers can be given, depending on the scenario:
open Expecto
test "greet works as expected" {
let output = ResizeArray<string>()
handle
greet ()
with
| (ConsoleRead ()) k ->
continue k "John Doe"
| (ConsoleWrite v) k ->
output.Add(v)
continue k ()
Expect.equal (Seq.toList output) [ "Hello John Doe" ] ""
}
A simulation of this feature might be possible with computation expressions and polymorphic variants.
The existing way of approaching this problem in F# is ...
- Impure code - convenient to write, but effects are unmanaged
- Monadic code - effect are managed, but code is inconvenient to write
Pros and Cons
The advantages of making this adjustment to F# are ...
- Convenience and approachability of impure code - "just write the thing"
- Encoding of effect information in the type-system
- Ability to write mock effect handlers for testing purposes
- Performance improvements over monadic and DI approaches, with appropriate inlining of handlers
The disadvantages of making this adjustment to F# are ...
- Language complexity
- Potential for increased indirection in library and application code
Extra information
Estimated cost (XS, S, M, L, XL, XXL): XXL
Related suggestions:
Effect propagation is related to polymorphic variants. Effect annotations relate to function purity analysis.
- https://github.com/fsharp/fslang-suggestions/issues/40
- https://github.com/fsharp/fslang-suggestions/issues/201
- https://github.com/fsharp/fslang-suggestions/issues/538
- https://github.com/fsharp/fslang-suggestions/issues/728
Affidavit (please submit!)
Please tick these items by placing a cross in the box:
- [x] This is not a question (e.g. like one you might ask on StackOverflow) and I have searched StackOverflow for discussions of this issue
- [x] This is a language change and not purely a tooling change (e.g. compiler bug, editor support, warning/error messages, new warning, non-breaking optimisation) belonging to the compiler and tooling repository
- [x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it
- [x] I have searched both open and closed suggestions on this site and believe this is not a duplicate
Please tick all that apply:
- [x] This is not a breaking change to the F# language design
- [x] I or my company would be willing to help implement and/or test this
For Readers
If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.