Created
September 28, 2023 14:26
-
-
Save Savelenko/e721e3fb286f15104e5be2425f9cfe66 to your computer and use it in GitHub Desktop.
Early return computation expression in F#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(* An experiment with implementing early return in F#. Initial examples work, but I do not know whether it is really correct! *) | |
/// A computation which runs to completion and produces 'r or returns an 'a early, short-circuiting some steps. | |
type R<'a,'r> = | |
private | |
| Done of 'r | |
| EarlyReturn of 'a | |
| Zero | |
| Effect of (unit -> R<'a,'r>) | |
let rec bind f r = | |
match r with | |
| Done x -> f x | |
| EarlyReturn a -> EarlyReturn a | |
| Zero -> Zero | |
| Effect r -> Effect (r >> bind f) | |
let ret x = Done x | |
let map f p = bind (f >> Done) p | |
let rec run r = | |
match r with | |
// The Done and EarlyReturn cases force that, in the end, early return type is the same as the normal return type | |
| Done a -> a | |
| EarlyReturn a -> a | |
| Effect f -> run (f ()) | |
| Zero -> failwith "Zero is a technical case and should never be visible in the end" | |
// What we are after: early return a result short-circuiting the rest of the computation | |
let early a = EarlyReturn a | |
(* Computation expression support *) | |
type EarlyReturnBuilder () = | |
member _.Return(a) = ret a | |
member _.ReturnFrom(r : R<_,_>) = r | |
member _.Bind(r, f) = bind f r | |
member _.Delay(f) = Effect f | |
member _.Run(r) = run r | |
member _.Zero() = Zero | |
member _.Combine(r1, r2) = | |
match r1 with | |
| EarlyReturn _ -> r1 | |
| Zero -> r2 // Zero must be handled separately because `bind` below will discard r2 too otherwise | |
| _ -> r1 |> bind (fun _ -> r2) | |
let earlyReturn = EarlyReturnBuilder () | |
(* Examples *) | |
/// Does not reaturn early because it just returns. | |
let example1 n = earlyReturn { | |
return n | |
} | |
/// Return some fixed value early. | |
let example2 n = earlyReturn { | |
return! early 5 | |
return n | |
} | |
/// Combinations | |
let example3 n = earlyReturn { | |
// This should not have any effect, because example1 ~ id | |
let n = example1 n // Remarkably, let! is not needed here (which is even better)!!! Somehow this is due to Run. | |
// This does have an effect, try uncommenting | |
// let n = example2 n | |
// return! example2 n // This does not work; perhaps this is even good. We have the `early` "keyword" after all. | |
printfn $"n = {n}" // when example2 is uncommented n = 5 always here | |
if n < 7 then | |
printfn "Returning early with default value" | |
return! early 7 | |
printfn "Did not return early!" | |
return n | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment