Skip to content

Instantly share code, notes, and snippets.

@Savelenko
Created September 28, 2023 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Savelenko/e721e3fb286f15104e5be2425f9cfe66 to your computer and use it in GitHub Desktop.
Save Savelenko/e721e3fb286f15104e5be2425f9cfe66 to your computer and use it in GitHub Desktop.
Early return computation expression in F#
(* 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