Skip to content

Instantly share code, notes, and snippets.

@bigjonroberts
Last active December 17, 2022 00:21
Show Gist options
  • Select an option

  • Save bigjonroberts/34b91c65f11c4ac5c414ce112a40f7a8 to your computer and use it in GitHub Desktop.

Select an option

Save bigjonroberts/34b91c65f11c4ac5c414ce112a40f7a8 to your computer and use it in GitHub Desktop.
F# example of monadic functions with C# examples for Task<T>
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"[You Only Live Once](https://raw.githubusercontent.com/haf/YoLo/master/YoLo.fs)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
},
"polyglot_notebook": {
"kernelName": "fsharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"[<AutoOpen>]\n",
"module YoLo =\n",
"\n",
" open System\n",
" open System.Threading.Tasks\n",
"\n",
" let curry f a b = f (a, b)\n",
"\n",
" let uncurry f (a, b) = f a b\n",
"\n",
" let flip f a b = f b a\n",
"\n",
" let ct x = fun _ -> x\n",
"\n",
" module Choice =\n",
"\n",
" let create v = Choice1Of2 v\n",
"\n",
" let createSnd v = Choice2Of2 v\n",
"\n",
" let map f = function\n",
" | Choice1Of2 v -> Choice1Of2 (f v)\n",
" | Choice2Of2 msg -> Choice2Of2 msg\n",
"\n",
" let mapSnd f = function\n",
" | Choice1Of2 v -> Choice1Of2 v\n",
" | Choice2Of2 v -> Choice2Of2 (f v)\n",
"\n",
" let map2 f1 f2: Choice<'a, 'b> -> Choice<'c, 'd> = function\n",
" | Choice1Of2 v -> Choice1Of2 (f1 v)\n",
" | Choice2Of2 v -> Choice2Of2 (f2 v)\n",
"\n",
" let bind (f: 'a -> Choice<'b, 'c>) (v: Choice<'a, 'c>) =\n",
" match v with\n",
" | Choice1Of2 v -> f v\n",
" | Choice2Of2 c -> Choice2Of2 c\n",
"\n",
" let bindSnd (f: 'a -> Choice<'c, 'b>) (v: Choice<'c, 'a>) =\n",
" match v with\n",
" | Choice1Of2 x -> Choice1Of2 x\n",
" | Choice2Of2 x -> f x\n",
" \n",
" let fold f g =\n",
" function\n",
" | Choice1Of2 x -> f x\n",
" | Choice2Of2 y -> g y\n",
" \n",
" let apply f v =\n",
" bind (fun f' ->\n",
" bind (fun v' ->\n",
" create (f' v')) v) f\n",
"\n",
" let applySnd f v =\n",
" bind (fun f' ->\n",
" bindSnd (fun v' ->\n",
" createSnd (f' v')) v) f\n",
"\n",
" let lift2 f v1 v2 =\n",
" apply (apply (create f) v1) v2\n",
"\n",
" let lift3 f v1 v2 v3 =\n",
" apply (apply (apply (create f) v1) v2) v3\n",
"\n",
" let lift4 f v1 v2 v3 v4 =\n",
" apply (apply (apply (apply (create f) v1) v2) v3) v4\n",
"\n",
" let lift5 f v1 v2 v3 v4 v5 =\n",
" apply (apply (apply (apply (apply (create f) v1) v2) v3) v4) v5\n",
"\n",
" let ofOption onMissing = function\n",
" | Some x -> Choice1Of2 x\n",
" | None -> Choice2Of2 (onMissing ())\n",
"\n",
" let ofResult = function\n",
" | Ok x -> Choice1Of2 x\n",
" | Error x -> Choice2Of2 x\n",
"\n",
" let toResult = function\n",
" | Choice1Of2 x -> Ok x\n",
" | Choice2Of2 x -> Error x\n",
" \n",
" let orDefault value = function\n",
" | Choice1Of2 v -> v\n",
" | _ -> value ()\n",
"\n",
" let inject f = function\n",
" | Choice1Of2 x -> f x; Choice1Of2 x\n",
" | Choice2Of2 x -> Choice2Of2 x\n",
"\n",
" let injectSnd f = function\n",
" | Choice1Of2 x -> Choice1Of2 x\n",
" | Choice2Of2 x -> f x; Choice2Of2 x\n",
"\n",
" module Operators =\n",
"\n",
" let inline (>>=) m f =\n",
" bind f m\n",
"\n",
" let inline (>>-) m f = // snd\n",
" bindSnd f m\n",
"\n",
" let inline (=<<) f m =\n",
" bind f m\n",
"\n",
" let inline (-<<) f m = // snd\n",
" bindSnd f m\n",
"\n",
" let inline (>>*) m f =\n",
" inject f m\n",
"\n",
" let inline (>>@) m f = // snd\n",
" injectSnd f m\n",
"\n",
" let inline (<*>) f m =\n",
" apply f m\n",
"\n",
" let inline (<!>) f m =\n",
" map f m\n",
"\n",
" let inline (>!>) m f =\n",
" map f m\n",
"\n",
" let inline (<@>) f m = // snd\n",
" mapSnd f m\n",
"\n",
" let inline (>@>) m f = // snd\n",
" mapSnd f m\n",
"\n",
" let inline ( *>) m1 m2 =\n",
" lift2 (fun _ x -> x) m1 m2\n",
"\n",
" let inline ( <*) m1 m2 =\n",
" lift2 (fun x _ -> x) m1 m2\n",
"\n",
"\n",
" module Result =\n",
"\n",
" let map2 f1 f2: Result<'a, 'b> -> Result<'c, 'd> = function\n",
" | Ok v -> Ok (f1 v)\n",
" | Error v -> Error (f2 v)\n",
"\n",
" let bindError (f: 'a -> Result<'c, 'b>) (v: Result<'c, 'a>) =\n",
" match v with\n",
" | Ok x -> Ok x\n",
" | Error x -> f x\n",
" \n",
" let fold f g =\n",
" function\n",
" | Ok x -> f x\n",
" | Error y -> g y\n",
" \n",
" let apply f v =\n",
" Result.bind (fun f' ->\n",
" Result.bind (fun v' ->\n",
" Ok (f' v')) v) f\n",
"\n",
" let applyError f v =\n",
" Result.bind (fun f' ->\n",
" bindError (fun v' ->\n",
" Error (f' v')) v) f\n",
"\n",
" let lift2 f v1 v2 =\n",
" apply (apply (Ok f) v1) v2\n",
"\n",
" let lift3 f v1 v2 v3 =\n",
" apply (apply (apply (Ok f) v1) v2) v3\n",
"\n",
" let lift4 f v1 v2 v3 v4 =\n",
" apply (apply (apply (apply (Ok f) v1) v2) v3) v4\n",
"\n",
" let lift5 f v1 v2 v3 v4 v5 =\n",
" apply (apply (apply (apply (apply (Ok f) v1) v2) v3) v4) v5\n",
"\n",
" let ofOption onMissing = function\n",
" | Some x -> Ok x\n",
" | None -> Error onMissing\n",
"\n",
" let toChoice = function\n",
" | Ok x -> Choice1Of2 x\n",
" | Error x -> Choice2Of2 x\n",
"\n",
" let ofChoice = function\n",
" | Choice1Of2 x -> Ok x\n",
" | Choice2Of2 x -> Error x\n",
"\n",
" let inject f = function\n",
" | Ok x -> f x; Ok x\n",
" | Error x -> Error x\n",
"\n",
" let injectError f = function\n",
" | Ok x -> Ok x\n",
" | Error x -> f x; Error x\n",
"\n",
" module Operators =\n",
"\n",
" let inline (>>=) m f =\n",
" Result.bind f m\n",
"\n",
" let inline (>>-) m f = // snd\n",
" bindError f m\n",
"\n",
" let inline (=<<) f m =\n",
" Result.bind f m\n",
"\n",
" let inline (-<<) f m = // snd\n",
" bindError f m\n",
"\n",
" let inline (>>*) m f =\n",
" inject f m\n",
"\n",
" let inline (>>@) m f = // snd\n",
" injectError f m\n",
"\n",
" let inline (<*>) f m =\n",
" apply f m\n",
"\n",
" let inline (<!>) f m =\n",
" Result.map f m\n",
"\n",
" let inline (>!>) m f =\n",
" Result.map f m\n",
"\n",
" let inline (<@>) f m = // snd\n",
" Result.mapError f m\n",
"\n",
" let inline (>@>) m f = // snd\n",
" Result.mapError f m\n",
"\n",
" let inline ( *>) m1 m2 =\n",
" lift2 (fun _ x -> x) m1 m2\n",
"\n",
" let inline ( <*) m1 m2 =\n",
" lift2 (fun x _ -> x) m1 m2\n",
"\n",
" module Option =\n",
"\n",
" let create x = Some x\n",
"\n",
" let apply (f : ('a -> 'b) option) (v: 'a option) =\n",
" Option.bind (fun f' ->\n",
" Option.bind (fun v' ->\n",
" create (f' v')) v) f\n",
"\n",
" let lift2 f v1 v2 =\n",
" apply (apply (create f) v1) v2\n",
"\n",
" let lift3 f v1 v2 v3 =\n",
" apply (apply (apply (create f) v1) v2) v3\n",
"\n",
" let lift4 f v1 v2 v3 v4 =\n",
" apply (apply (apply (apply (create f) v1) v2) v3) v4\n",
"\n",
" let lift5 f v1 v2 v3 v4 v5 =\n",
" apply (apply (apply (apply (apply (create f) v1) v2) v3) v4) v5\n",
"\n",
" let ofChoice = function\n",
" | Choice1Of2 x -> Some x\n",
" | _ -> None\n",
"\n",
" let toChoice case2 = function\n",
" | Some x -> Choice1Of2 x\n",
" | None -> Choice2Of2 (case2 ())\n",
"\n",
" let ofNullable nullable: 'a option =\n",
" match box nullable with\n",
" | null -> None // CLR null\n",
" | :? Nullable<_> as n when not n.HasValue -> None // CLR struct\n",
" | :? Nullable<_> as n when n.HasValue -> Some (n.Value) // CLR struct\n",
" | x when x.Equals (DBNull.Value) -> None // useful when reading from the db into F#\n",
" | x -> Some (unbox x) // anything else\n",
"\n",
" let toNullable = function\n",
" | Some item -> new Nullable<_>(item)\n",
" | None -> new Nullable<_>()\n",
"\n",
" let orDefault x = function\n",
" | None -> x ()\n",
" | Some y -> y\n",
"\n",
" let inject f = function\n",
" | Some x -> f x; Some x\n",
" | None -> None\n",
"\n",
" module Operators =\n",
"\n",
" let inline (>>=) m f =\n",
" Option.bind f m\n",
"\n",
" let inline (=<<) f m =\n",
" Option.bind f m\n",
"\n",
" let inline (>>*) m f =\n",
" inject f m\n",
"\n",
" let inline (<*>) f m =\n",
" apply f m\n",
"\n",
" let inline (<!>) f m =\n",
" Option.map f m\n",
"\n",
" let inline ( *>) m1 m2 =\n",
" lift2 (fun _ x -> x) m1 m2\n",
"\n",
" let inline ( <*) m1 m2 =\n",
" lift2 (fun x _ -> x) m1 m2\n",
"\n",
" type Base64String = string\n",
"\n",
" module String =\n",
" open System.Globalization // needed when using DNXCORE50\n",
" open System.IO\n",
" open System.Security.Cryptography\n",
"\n",
" /// Also, invariant culture\n",
" let equals (a: string) (b: string) =\n",
" #if DNXCORE50\n",
" (CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.None)).Equals(a, b)\n",
" #else\n",
" a.Equals(b, StringComparison.InvariantCulture)\n",
" #endif\n",
"\n",
" /// Also, invariant culture\n",
" let equalsCaseInsensitive (a: string) (b: string) =\n",
" #if DNXCORE50\n",
" (CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreCase)).Equals(a, b)\n",
" #else\n",
" a.Equals(b, StringComparison.InvariantCultureIgnoreCase)\n",
" #endif\n",
" \n",
" /// Compare ordinally with ignore case.\n",
" let equalsOrdinalCI (str1: string) (str2: string) =\n",
" String.Equals(str1, str2, StringComparison.OrdinalIgnoreCase)\n",
"\n",
" /// Ordinally compare two strings in constant time, bounded by the length of the\n",
" /// longest string.\n",
" let equalsConstantTime (str1: string) (str2: string) =\n",
" let mutable xx = uint32 str1.Length ^^^ uint32 str2.Length\n",
" let mutable i = 0\n",
" while i < str1.Length && i < str2.Length do\n",
" xx <- xx ||| uint32 (int str1.[i] ^^^ int str2.[i])\n",
" i <- i + 1\n",
" xx = 0u\n",
"\n",
" let toLowerInvariant (str: string) =\n",
" str.ToLowerInvariant()\n",
"\n",
" let replace (find: string) (replacement: string) (str: string) =\n",
" str.Replace(find, replacement)\n",
"\n",
" let isEmpty (s: string) =\n",
" s.Length = 0\n",
"\n",
" let trim (s: string) =\n",
" s.Trim()\n",
" \n",
" let trimc (toTrim: char) (s: string) =\n",
" s.Trim toTrim\n",
" \n",
" let trimStart (s: string) =\n",
" s.TrimStart()\n",
" \n",
" let split (c: char) (s: string) =\n",
" s.Split c |> Array.toList\n",
" \n",
" let splita (c: char) (s: string) =\n",
" s.Split c\n",
" \n",
" let startsWith (substring: string) (s: string) =\n",
" s.StartsWith substring\n",
" \n",
" let contains (substring: string) (s: string) =\n",
" s.Contains substring\n",
" \n",
" let substring index (s: string) =\n",
" s.Substring index\n",
"\n",
" module Bytes =\n",
" open System.IO\n",
" open System.Linq\n",
" open System.Security.Cryptography\n",
"\n",
" let hash (algo: unit -> #HashAlgorithm) (bs: byte[]) =\n",
" use ms = new MemoryStream()\n",
" ms.Write(bs, 0, bs.Length)\n",
" ms.Seek(0L, SeekOrigin.Begin) |> ignore\n",
" use sha = algo ()\n",
" sha.ComputeHash ms\n",
"\n",
" let sha1 =\n",
" #if DNXCORE50\n",
" hash (fun () -> SHA1.Create())\n",
" #else\n",
" hash (fun () -> new SHA1Managed())\n",
" #endif\n",
"\n",
" let sha256 =\n",
" #if DNXCORE50\n",
" hash (fun () -> SHA256.Create())\n",
" #else\n",
" hash (fun () -> new SHA256Managed())\n",
" #endif\n",
"\n",
" let sha512 =\n",
" #if DNXCORE50\n",
" hash (fun () -> SHA512.Create())\n",
" #else\n",
" hash (fun () -> new SHA512Managed())\n",
" #endif\n",
"\n",
" let toHex (bs: byte[]) =\n",
" BitConverter.ToString bs\n",
" |> String.replace \"-\" \"\"\n",
" |> String.toLowerInvariant\n",
"\n",
" let ofHex (digestString: string) =\n",
" Enumerable.Range(0, digestString.Length)\n",
" .Where(fun x -> x % 2 = 0)\n",
" .Select(fun x -> Convert.ToByte(digestString.Substring(x, 2), 16))\n",
" .ToArray()\n",
"\n",
" /// Compare two byte arrays in constant time, bounded by the length of the\n",
" /// longest byte array.\n",
" let equalsConstantTime (bits: byte []) (bobs: byte []) =\n",
" let mutable xx = uint32 bits.Length ^^^ uint32 bobs.Length\n",
" let mutable i = 0\n",
" while i < bits.Length && i < bobs.Length do\n",
" xx <- xx ||| uint32 (bits.[i] ^^^ bobs.[i])\n",
" i <- i + 1\n",
" xx = 0u\n",
"\n",
" [<RequireQualifiedAccess>]\n",
" module Culture =\n",
" open System.Globalization\n",
"\n",
" let invariant = CultureInfo.InvariantCulture\n",
"\n",
" module UTF8 =\n",
" open System.Text\n",
"\n",
" let private utf8 = Encoding.UTF8\n",
"\n",
" /// Convert the full buffer `b` filled with UTF8-encoded strings into a CLR\n",
" /// string.\n",
" let toString (bs: byte []) =\n",
" utf8.GetString bs\n",
"\n",
" /// Convert the byte array to a string, by indexing into the passed buffer `b`\n",
" /// and taking `count` bytes from it.\n",
" let toStringAtOffset (b: byte []) (index: int) (count: int) =\n",
" utf8.GetString(b, index, count)\n",
"\n",
" /// Get the UTF8-encoding of the string.\n",
" let bytes (s: string) =\n",
" utf8.GetBytes s\n",
"\n",
" /// Convert the passed string `s` to UTF8 and then encode the buffer with\n",
" /// base64.\n",
" let encodeBase64: string -> Base64String =\n",
" bytes >> Convert.ToBase64String\n",
"\n",
" /// Convert the passed string `s`, assumed to be a valid Base64 encoding, to a\n",
" /// CLR string, going through UTF8.\n",
" let decodeBase64: Base64String -> string =\n",
" Convert.FromBase64String >> toString\n",
"\n",
" let sha1 =\n",
" bytes >> Bytes.sha1\n",
"\n",
" let sha1Hex =\n",
" bytes >> Bytes.sha1 >> Bytes.toHex\n",
"\n",
" let sha256 =\n",
" bytes >> Bytes.sha256\n",
"\n",
" let sha256Hex =\n",
" bytes >> Bytes.sha256 >> Bytes.toHex\n",
"\n",
" let sha512 =\n",
" bytes >> Bytes.sha512\n",
"\n",
" let sha512Hex =\n",
" bytes >> Bytes.sha512 >> Bytes.toHex\n",
"\n",
" module Comparisons =\n",
"\n",
" /// compare x to yobj mapped on selected value from function f\n",
" let compareOn f x (yobj: obj) =\n",
" match yobj with\n",
" | :? 'T as y -> compare (f x) (f y)\n",
" | _ -> invalidArg \"yobj\" \"cannot compare values of different types\"\n",
"\n",
" /// check equality on x and y mapped on selected value from function f\n",
" let equalsOn f x (yobj:obj) =\n",
" match yobj with\n",
" | :? 'T as y -> (f x = f y)\n",
" | _ -> false\n",
"\n",
" /// hash x on the selected value from f\n",
" let hashOn f x = hash (f x)\n",
"\n",
" type Random with\n",
" /// generate a new random ulong64 value\n",
" member x.NextUInt64() =\n",
" let buffer = Array.zeroCreate<byte> sizeof<UInt64>\n",
" x.NextBytes buffer\n",
" BitConverter.ToUInt64(buffer, 0)\n",
"\n",
" module Array =\n",
"\n",
" /// Ordinally compare two arrays in constant time, bounded by the length of the\n",
" /// longest array. This function uses the F# language equality.\n",
" let equalsConstantTime (arr1: 'a []) (arr2: 'a []) =\n",
" if arr1.Length <> arr2.Length then false else\n",
" let mutable b = true\n",
" for i in 0 .. arr1.Length - 1 do\n",
" b <- b && (arr1.[i] = arr2.[i])\n",
" b\n",
"\n",
" /// Returns a sequence that yields chunks of length n.\n",
" /// Each chunk is returned as an array.\n",
" /// Thanks to\n",
" /// https://nbevans.wordpress.com/2014/03/13/really-simple-way-to-split-a-f-sequence-into-chunks-partitions/\n",
" let chunk (n: uint32) (s: seq<'t>) = seq {\n",
" let n = int n\n",
" let pos = ref 0\n",
" let buffer = Array.zeroCreate<'t> n\n",
"\n",
" for x in s do\n",
" buffer.[!pos] <- x\n",
" if !pos = n - 1 then\n",
" yield buffer |> Array.copy\n",
" pos := 0\n",
" else\n",
" incr pos\n",
"\n",
" if !pos > 0 then\n",
" yield Array.sub buffer 0 !pos\n",
" }\n",
"\n",
" module Regex =\n",
" open System.Text.RegularExpressions\n",
"\n",
" type RegexMatch = Match\n",
"\n",
" let escape input =\n",
" Regex.Escape input\n",
"\n",
" let split pattern input =\n",
" Regex.Split(input, pattern)\n",
" |> List.ofArray\n",
"\n",
" let replace pattern replacement input =\n",
" Regex.Replace(input, pattern, (replacement: string))\n",
"\n",
" let replaceWithFunction pattern (replaceFunc: RegexMatch -> string) input =\n",
" Regex.Replace(input, pattern, replaceFunc)\n",
"\n",
" /// Match the `input` against the regex `pattern`. You can do a\n",
" /// `Seq.cast<Group>` on the result to get it as a sequence\n",
" /// and also index with `.[\"name\"]` into the result if you have\n",
" /// named capture groups.\n",
" let ``match`` pattern input =\n",
" match Regex.Matches(input, pattern) with\n",
" | x when x.Count > 0 ->\n",
" x\n",
" |> Seq.cast<Match>\n",
" |> Seq.head\n",
" |> fun x -> x.Groups\n",
" |> Some\n",
" | _ -> None\n",
"\n",
" type Microsoft.FSharp.Control.Async with\n",
" /// Raise an exception on the async computation/workflow.\n",
" static member AsyncRaise (e: exn) =\n",
" Async.FromContinuations(fun (_,econt,_) -> econt e)\n",
"\n",
" /// Await a task asynchronously\n",
" static member AwaitTask (t: Task) =\n",
" let flattenExns (e: AggregateException) = e.Flatten().InnerExceptions.[0]\n",
" let rewrapAsyncExn (it: Async<unit>) =\n",
" async { try do! it with :? AggregateException as ae -> do! Async.AsyncRaise (flattenExns ae) }\n",
" let tcs = new TaskCompletionSource<unit>(TaskCreationOptions.None)\n",
" t.ContinueWith((fun t' ->\n",
" if t.IsFaulted then tcs.SetException(t.Exception |> flattenExns)\n",
" elif t.IsCanceled then tcs.SetCanceled ()\n",
" else tcs.SetResult(())), TaskContinuationOptions.ExecuteSynchronously)\n",
" |> ignore\n",
" tcs.Task |> Async.AwaitTask |> rewrapAsyncExn\n",
"\n",
" type Microsoft.FSharp.Control.AsyncBuilder with\n",
" /// An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on\n",
" /// a standard .NET task\n",
" member x.Bind(t: Task<'T>, f:'T -> Async<'R>): Async<'R> =\n",
" async.Bind(Async.AwaitTask t, f)\n",
"\n",
" /// An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on\n",
" /// a standard .NET task which does not commpute a value\n",
" member x.Bind(t: Task, f: unit -> Async<'R>): Async<'R> =\n",
" async.Bind(Async.AwaitTask t, f)\n",
"\n",
" module Async =\n",
"\n",
" let result = async.Return\n",
"\n",
" let map f value = async {\n",
" let! v = value\n",
" return f v\n",
" }\n",
"\n",
" let bind f xAsync = async {\n",
" let! x = xAsync\n",
" return! f x\n",
" }\n",
"\n",
" let withTimeout timeoutMillis operation =\n",
" async {\n",
" let! child = Async.StartChild(operation, timeoutMillis)\n",
" try\n",
" let! result = child\n",
" return Some result\n",
" with :? TimeoutException ->\n",
" return None\n",
" }\n",
"\n",
" let apply fAsync xAsync = async {\n",
" // start the two asyncs in parallel\n",
" let! fChild = Async.StartChild fAsync\n",
" let! xChild = Async.StartChild xAsync\n",
"\n",
" // wait for the results\n",
" let! f = fChild\n",
" let! x = xChild\n",
"\n",
" // apply the function to the results\n",
" return f x\n",
" }\n",
"\n",
" let lift2 f x y =\n",
" apply (apply (result f) x) y\n",
"\n",
" let lift3 f x y z =\n",
" apply (apply (apply (result f) x) y) z\n",
"\n",
" let lift4 f x y z a =\n",
" apply (apply (apply (apply (result f) x) y) z) a\n",
"\n",
" let lift5 f x y z a b =\n",
" apply (apply (apply (apply (apply (result f) x) y) z) a) b\n",
"\n",
" module Operators =\n",
"\n",
" let inline (>>=) m f =\n",
" bind f m\n",
"\n",
" let inline (=<<) f m =\n",
" bind f m\n",
"\n",
" let inline (<*>) f m =\n",
" apply f m\n",
"\n",
" let inline (<!>) f m =\n",
" map f m\n",
"\n",
" let inline ( *>) m1 m2 =\n",
" lift2 (fun _ x -> x) m1 m2\n",
"\n",
" let inline ( <*) m1 m2 =\n",
" lift2 (fun x _ -> x) m1 m2\n",
"\n",
" /// Stacks the Async Result monad.\n",
" /// All functions from Yolo's Result are implemented, so it's easy to replace it.\n",
" module AsyncResult =\n",
"\n",
" // https://fsharpforfunandprofit.com/posts/elevated-world-5/#asynclist\n",
"\n",
" let ok v = v |> Ok |> Async.result\n",
"\n",
" let error v = v |> Error |> Async.result\n",
"\n",
" let map f x =\n",
" f |> Result.map |> Async.map <| x\n",
"\n",
" let map2 f1 f2 x =\n",
" Result.map2 f1 f2 |> Async.map <| x\n",
"\n",
" let mapError f x =\n",
" Result.mapError f |> Async.map <| x\n",
"\n",
" let fold f g x =\n",
" Result.fold f g |> Async.map <| x\n",
"\n",
" let bind f xAR = async {\n",
" let! x = xAR\n",
" match x with\n",
" | Ok x -> return! f x\n",
" | Error e -> return Error e\n",
" }\n",
"\n",
" let bindError f xAR = async {\n",
" let! x = xAR\n",
" match x with\n",
" | Ok x -> return Ok x\n",
" | Error e -> return! f e\n",
" }\n",
"\n",
" let bindResult f xAR = async {\n",
" let! x = xAR\n",
" match x with\n",
" | Ok x -> return f x\n",
" | Error e -> return e |> Error\n",
" }\n",
"\n",
" let bindAsync f xAR = async {\n",
" let! x = xAR\n",
" match x with\n",
" | Ok x -> return! f >> Async.Catch >> Async.map Result.ofChoice <| x\n",
" | Error e -> return e |> Error\n",
" }\n",
"\n",
" let apply f x =\n",
" f |> Async.bind (fun fR ->\n",
" x |> Async.map (fun xR ->\n",
" Result.apply fR xR))\n",
"\n",
" let applyResult f x =\n",
" f |> Result.apply |> Async.map <| x\n",
"\n",
" let lift2 f v1 v2 =\n",
" apply (apply (ok f) v1) v2\n",
"\n",
" let lift3 f v1 v2 v3 =\n",
" apply (apply (apply (ok f) v1) v2) v3\n",
"\n",
" let lift4 f v1 v2 v3 v4 =\n",
" apply (apply (apply (apply (ok f) v1) v2) v3) v4\n",
"\n",
" let lift5 f v1 v2 v3 v4 v5 =\n",
" apply (apply (apply (apply (apply (ok f) v1) v2) v3) v4) v5\n",
"\n",
" let ofOption onMissing = function\n",
" | Some x -> ok x\n",
" | None -> error onMissing\n",
"\n",
" let ofAsyncOption onMissing x =\n",
" onMissing |> ofOption |> Async.bind <| x\n",
"\n",
" let toChoice x =\n",
" fold Choice1Of2 Choice2Of2 <| x\n",
"\n",
" let ofChoice = function\n",
" | Choice1Of2 x -> ok x\n",
" | Choice2Of2 x -> error x\n",
"\n",
" let ofAsyncChoice x =\n",
" ofChoice |> Async.bind <| x\n",
"\n",
" let inject f x =\n",
" map ( fun x -> f x; x ) x\n",
"\n",
" let injectError f x =\n",
" map2 id ( fun x -> f x; x ) x\n",
"\n",
" // we love syntatic suggar.\n",
" module Operators =\n",
"\n",
" let inline (>>=) m f =\n",
" bind f m\n",
"\n",
" let inline (>>-) m f = // snd\n",
" bindError f m\n",
"\n",
" let inline (=<<) f m =\n",
" bind f m\n",
"\n",
" //vscode/ionide has a highlighter bug, so we use comment (***) to fix it\n",
" let inline (-<< (***) ) f m = // snd\n",
" bindError f m\n",
"\n",
" let inline (>>*) m f =\n",
" inject f m\n",
"\n",
" let inline (>>@) m f = // snd\n",
" injectError f m\n",
"\n",
" let inline (<*>) f m =\n",
" apply f m\n",
"\n",
" let inline (<!>) f m =\n",
" map f m\n",
"\n",
" let inline (>!>) m f =\n",
" map f m\n",
"\n",
" let inline (<@>) f m = // snd\n",
" mapError f m\n",
"\n",
" let inline (>@>) m f = // snd\n",
" mapError f m\n",
"\n",
" let inline ( *>) m1 m2 =\n",
" lift2 (fun _ x -> x) m1 m2\n",
"\n",
" let inline ( <*) m1 m2 =\n",
" lift2 (fun x _ -> x) m1 m2\n",
"\n",
" module List =\n",
"\n",
" /// Split xs at n, into two lists, or where xs ends if xs.Length < n.\n",
" let split n xs =\n",
" let rec splitUtil n xs acc =\n",
" match xs with\n",
" | [] -> List.rev acc, []\n",
" | _ when n = 0u -> List.rev acc, xs\n",
" | x::xs' -> splitUtil (n - 1u) xs' (x::acc)\n",
" splitUtil n xs []\n",
"\n",
" /// Chunk a list into pageSize large chunks\n",
" let chunk pageSize = function\n",
" | [] -> None\n",
" | l -> let h, t = l |> split pageSize in Some(h, t)\n",
"\n",
" let first = function\n",
" | [] -> None\n",
" | x :: _ -> Some x\n",
"\n",
" // Description of the below functions:\n",
" // http://fsharpforfunandprofit.com/posts/elevated-world-5/#asynclist\n",
"\n",
" /// Map a Async producing function over a list to get a new Async using\n",
" /// applicative style. ('a -> Async<'b>) -> 'a list -> Async<'b list>\n",
" let rec traverseAsyncA f list =\n",
" let (<*>) = Async.apply\n",
" let cons head tail = head :: tail\n",
" let initState = Async.result []\n",
" let folder head tail =\n",
" Async.result cons <*> (f head) <*> tail\n",
"\n",
" List.foldBack folder list initState\n",
"\n",
" /// Transform a \"list<Async>\" into a \"Async<list>\" and collect the results\n",
" /// using apply.\n",
" let sequenceAsyncA x = traverseAsyncA id x\n",
"\n",
" /// Map a Choice-producing function over a list to get a new Choice using\n",
" /// applicative style. ('a -> Choice<'b, 'c>) -> 'a list -> Choice<'b list, 'c>\n",
" let rec traverseChoiceA f list =\n",
" let (<*>) = Choice.apply\n",
" let cons head tail = head :: tail\n",
"\n",
" // right fold over the list\n",
" let initState = Choice.create []\n",
" let folder head tail =\n",
" Choice.create cons <*> (f head) <*> tail\n",
"\n",
" List.foldBack folder list initState\n",
"\n",
" /// Transform a \"list<Choice>\" into a \"Choice<list>\" and collect the results\n",
" /// using apply.\n",
" let sequenceChoiceA x = traverseChoiceA id x\n",
"\n",
" /// Map a Result-producing function over a list to get a new Result using\n",
" /// applicative style. ('a -> Result<'b, 'c>) -> 'a list -> Result<'b list, 'c>\n",
" let rec traverseResultA f list =\n",
" let (<*>) = Result.apply\n",
" let cons head tail = head :: tail\n",
"\n",
" // right fold over the list\n",
" let initState = Result.Ok []\n",
" let folder head tail =\n",
" Result.Ok cons <*> (f head) <*> tail\n",
"\n",
" List.foldBack folder list initState\n",
"\n",
" /// Transform a \"list<Result>\" into a \"Result<list>\" and collect the results\n",
" /// using apply.\n",
" let sequenceResultA x = traverseResultA id x\n",
"\n",
" /// Map an AsyncResult producing function over a list to get a new AsyncResult using\n",
" /// applicative style. ('a -> Async<Result<'b, 'c>>) -> 'a list -> Async<Result<'b list, 'c>>\n",
" let rec traverseAsyncResultA f list =\n",
" let (<*>) = AsyncResult.apply\n",
" let cons head tail = head :: tail\n",
"\n",
" // right fold over the list\n",
" let initState = AsyncResult.ok []\n",
" let folder head tail =\n",
" AsyncResult.ok cons <*> (f head) <*> tail\n",
"\n",
" List.foldBack folder list initState\n",
"\n",
" /// Transform a \"list<Async<Result<'a, 'b>>\" into a \"Async<Result<'a list, 'b>>\" and collect the results\n",
" /// using apply.\n",
" let sequenceAsyncResultA x = traverseAsyncResultA id x\n",
"\n",
" /// Map an AsyncResult producing function over a list to get a new AsyncResult\n",
" /// using monadic style. ('a -> Async<Result<'b, 'c>>) -> 'a list -> Async<Result<'b list, 'c>>\n",
" let rec traverseAsyncResultM f list =\n",
" let (<*>) = AsyncResult.apply\n",
" let (>>=) m f = AsyncResult.bind f m\n",
" let cons head tail = head :: tail\n",
"\n",
" let initState = AsyncResult.ok []\n",
" let folder head tail =\n",
" f head >>= (fun h ->\n",
" tail >>= (fun t ->\n",
" AsyncResult.ok (cons h t) ))\n",
" List.foldBack folder list initState\n",
"\n",
" /// Transform a \"list<Async<Result<'a, 'b>>\" into a \"Async<Result<'a list, 'b>>\" and collect the results\n",
" /// using bind.\n",
" let sequenceAsyncResultM x = traverseAsyncResultM id x\n",
"\n",
" module Seq =\n",
"\n",
" let combinations size set =\n",
" let rec combinations' acc size set =\n",
" seq {\n",
" match size, set with\n",
" | n, x::xs ->\n",
" if n > 0 then yield! combinations' (x::acc) (n - 1) xs\n",
" if n >= 0 then yield! combinations' acc n xs\n",
" | 0, [] -> yield acc\n",
" | _, [] -> ()\n",
" }\n",
" combinations' [] size set\n",
"\n",
" let first (xs: _ seq): _ option =\n",
" if Seq.isEmpty xs then None else Seq.head xs |> Some\n",
"\n",
" module Env =\n",
"\n",
" let var (k: string) =\n",
" let v = Environment.GetEnvironmentVariable k\n",
" if isNull v then None else Some v\n",
"\n",
" let varParse parse (k: string) =\n",
" var k |> Option.map parse\n",
"\n",
" let varDefault (key: String) (getDefault: unit -> string) =\n",
" match var key with\n",
" | Some v -> v\n",
" | None -> getDefault ()\n",
"\n",
" let varDefaultParse parse (key: string) getDefault =\n",
" varDefault key getDefault |> parse\n",
"\n",
" let varRequired (k: String) =\n",
" match var k with\n",
" | Some v -> v\n",
" | None -> failwithf \"The environment variable '%s' is missing.\" k\n",
"\n",
" module App =\n",
"\n",
" open System.IO\n",
" open System.Reflection\n",
"\n",
" /// Gets the calling assembly's informational version number as a string\n",
" let getVersion () =\n",
" #if DNXCORE50\n",
" (typeof<Random>.GetTypeInfo().Assembly)\n",
" #else\n",
" Assembly.GetCallingAssembly()\n",
" #endif\n",
" .GetCustomAttribute<AssemblyInformationalVersionAttribute>()\n",
" .InformationalVersion\n",
"\n",
" /// Get the assembly resource\n",
" let resourceIn (assembly: Assembly) name =\n",
" use stream = assembly.GetManifestResourceStream name\n",
" if stream = null then\n",
" assembly.GetManifestResourceNames()\n",
" |> Array.fold (fun s t -> sprintf \"%s\\n - %s\" s t) \"\"\n",
" |> sprintf \"couldn't find resource named '%s', from: %s\" name\n",
" |> Choice2Of2\n",
" else\n",
" use reader = new StreamReader(stream)\n",
" reader.ReadToEnd ()\n",
" |> Choice1Of2\n",
"\n",
" /// Get the current assembly resource\n",
" let resource =\n",
" #if DNXCORE50\n",
" let assembly = typeof<Random>.GetTypeInfo().Assembly\n",
" #else\n",
" let assembly = Assembly.GetExecutingAssembly ()\n",
" #endif\n",
" resourceIn assembly\n",
"\n",
" module Dictionary =\n",
" open System.Collections.Generic\n",
" \n",
" /// Attempts to retrieve a value as an option from a dictionary using the provided key\n",
" let tryFind key (dict: Dictionary<_, _>) =\n",
" match dict.TryGetValue key with\n",
" | true, value -> Some value\n",
" | _ -> None\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"# Either Monad variants"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
},
"polyglot_notebook": {
"kernelName": "fsharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"type Option<'a> =\n",
" | Some of 'a\n",
" | None\n",
"\n",
"type Result<'a,'b> =\n",
" | Ok of 'a\n",
" | Error of 'b\n",
"\n",
"type Choice1of2<'a,'b> =\n",
" | Choice1 of 'a\n",
" | Choice2 of 'b\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"# Translating LINQ verbs to traditional functional names\n",
"\n",
"| Linq or Async Operation in C# | Functional Operation | Functional Signature |\n",
"|---|---|---|\n",
"| `Select` | `map` | `('a -> 'b) -> Option<'a> -> Option<'b>` |\n",
"| ? | apply | Option< a' -> b'> -> Option<'a> -> Option<'b> |\n",
"| task.ContinueWith | bind | (a' -> Task<'a>) -> Task<'a> -> Task<'b> |\n",
"| await | let! | (a' -> Task<'a>) -> Task<'a> -> Task<'b> |"
]
},
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"## Map and Apply"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
},
"polyglot_notebook": {
"kernelName": "fsharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"let a = Some 1\n",
"let b = Some 2\n",
"let a2 = a |> Option.map (fun x -> x + 1)\n",
"let add2 = Option.map (fun x -> x + 2)\n",
"let add = Option.map2 (+)\n",
"let addOption = Some (fun x -> x + 3)\n",
"let add3 = Option.apply addOption\n",
"let c = add a b\n",
"printfn \"c = %O\" c\n",
"\n",
"\n",
"let addToOption x = x + 4 |> Some\n",
"let add4 = Option.bind addToOption\n",
"\n",
"let d = c |> Option.defaultValue 0\n",
"d\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"using System.Collections.Generic;\n",
"using System.Linq;\n",
"\n",
"var intArray = new [] { 1, 2, 3 };\n",
"\n",
"IEnumerable<int> Map (Func<int,int> f, IEnumerable<int> xs)\n",
"{\n",
" return xs.Select(f);\n",
"}\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"Experimenting with Option"
]
},
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"## Bind"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"using System;\n",
"using System.Threading.Tasks;\n",
"\n",
"// this is a \"bind\" function created similar to the YoLo Async.bind\n",
"async Task<U> bind<T,U> (Func<T,Task<U>> f, Task<T> x)\n",
"{\n",
" var x2 = await x;\n",
" var y = await f(x2);\n",
" return y;\n",
"}\n",
"\n",
"// overriding the \"bind\" function, as we can't use partial application like we do in F# later\n",
"Func<Task<T>,Task<U>> bind<T,U> (Func<T,Task<U>> f)\n",
"{\n",
" Func<Task<T>,Task<U>> f2 = default(Func<Task<T>,Task<U>>);\n",
" f2 = async x =>\n",
" {\n",
" var x2 = await x;\n",
" var y = await f(x2); \n",
" return y;\n",
" };\n",
" return f2;\n",
"}\n",
"\n",
"// this is an \"apply\" function created similar to the YoLo Async.apply\n",
"async Task<U> apply<T,U> (Task<Func<T,U>> fAsync, Task<T> xAsync)\n",
"{\n",
" var f = await fAsync;\n",
" var x = await xAsync;\n",
" return f(x);\n",
"}\n",
"\n",
"// overriding the \"apply\" function, as we can't use partial application like we do in F# later\n",
"Func<Task<T>,Task<U>> apply<T,U> (Task<Func<T,U>> fAsync)\n",
"{\n",
" Func<Task<T>,Task<U>> f2 = default(Func<Task<T>,Task<U>>);\n",
" f2 = async x =>\n",
" {\n",
" var f = await fAsync;\n",
" var x2 = await x;\n",
" var y = f(x2); \n",
" return y;\n",
" };\n",
" return f2;\n",
"}\n",
"\n",
"\n",
"\n",
"string add (int x)\n",
"{\n",
" return (x + 1).ToString();\n",
"}\n",
"\n",
"Func<int,Task<string>> addAsync = x =>\n",
"{\n",
" var y = add(x);\n",
" return Task.FromResult(y);\n",
"};\n",
"\n",
"int mult (string x)\n",
"{\n",
" return Int32.Parse(x) * 2;\n",
"}\n",
"\n",
"Func<string,Task<int>> multAsync = y =>\n",
"{\n",
" var z = mult(y);\n",
" return Task.FromResult(z);\n",
"};\n",
"\n",
"// using the \"bind\" function\n",
"Func<int,Task<int>> addAndMultAsync = x =>\n",
"{\n",
" var y = addAsync(x);\n",
" return bind(multAsync,y);\n",
"};\n",
"\n",
"var boundAddAsync = bind(addAsync);\n",
"var boundMultAsync = bind(multAsync);\n",
"\n",
"Func<int,Task<int>> addAndMultAsync2 = x =>\n",
"{\n",
" var y = addAsync(x);\n",
" return boundMultAsync(y);\n",
"};\n",
"\n",
"Func<int,Task<int>> addAndMultAsync3 = x =>\n",
"{\n",
" return boundMultAsync(addAsync(x));\n",
"};\n",
"\n",
"async Task<int> addAndMultAsync4 (int x)\n",
"{\n",
" var y = await\n",
" addAsync(x)\n",
" .ContinueWith(y => multAsync(y.Result)).Unwrap();\n",
" return y;\n",
"}\n",
"\n",
"int addAndMult (int x)\n",
"{\n",
" return mult(add(x));\n",
"}\n",
"\n",
"Func<int, int> addAndMultFunc = x => addAndMult(x);\n",
"var addAndMultFuncAsync = Task.FromResult(addAndMultFunc);\n",
"var liftedAddAndMult = apply(addAndMultFuncAsync);\n",
"\n",
"async Task<(int,int,int,int,int)> checkAll ()\n",
"{\n",
" var one = await addAndMultAsync(4);\n",
" var two = await addAndMultAsync2(4);\n",
" var three = await addAndMultAsync3(4);\n",
" var four = await addAndMultAsync4(4);\n",
" var five = await liftedAddAndMult(Task.FromResult(4));\n",
" return (one,two,three,four,five);\n",
"}\n",
"\n",
"var mainTask = checkAll();\n",
"\n",
"mainTask.Wait();\n",
"\n",
"return mainTask.Result;"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
},
"polyglot_notebook": {
"kernelName": "fsharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"let add (a: int) = a + 1 |> string\n",
"let addAsync = add >> Async.result\n",
"\n",
"let mult (a: string) = a |> System.Int32.Parse |> (*) 2\n",
"let multAsync = mult >> Async.result\n",
"\n",
"let addAndMultAsync (a: int) =\n",
" let x = addAsync a\n",
" x |> Async.bind (fun x' -> multAsync x')\n",
"\n",
"let boundAddAsync = Async.bind addAsync\n",
"let boundMultAsync = Async.bind multAsync\n",
"let addAndMultAsync2 (a:int) = \n",
" let x = addAsync a\n",
" boundMultAsync x\n",
"\n",
"let addAndMultAsync3 = addAsync >> boundMultAsync\n",
"\n",
"let addAndMult = add >> mult\n",
"\n",
"let liftedAddAndMult = addAndMult |> Async.result |> Async.apply\n",
"\n",
"let liftedAddAndMult = Async.map addAndMult\n",
"\n",
"async {\n",
" let! one = 4 |> addAndMultAsync\n",
" let! two = 4 |> addAndMultAsync2\n",
" let! three = 4 |> addAndMultAsync3\n",
" let! four = 4 |> Async.result |> liftedAddAndMult\n",
" return (one, two, three, four)\n",
"}\n",
"|> Async.RunSynchronously\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"# Serialization"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
},
"polyglot_notebook": {
"kernelName": "fsharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"#!fsharp\n",
"#r \"nuget: Newtonsoft.Json\"\n",
"open Newtonsoft.Json\n",
"\n",
"type Foo =\n",
" { F1: string\n",
" F2: int }\n",
"\n",
"type Bar =\n",
" { B1: int\n",
" B2: string }\n",
"\n",
"type Baz =\n",
" | Fooey of Foo\n",
" | Bart of Bar\n",
"\n",
"let data =\n",
" [ Fooey { F1 = \"bleh\"; F2 = 1 }\n",
" Bart { B1 = 0; B2 = \"meh\" } ]\n",
"JsonConvert.SerializeObject(data)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
},
"polyglot_notebook": {
"kernelInfo": {
"defaultKernelName": "csharp",
"items": [
{
"aliases": [
"c#",
"C#"
],
"languageName": "C#",
"name": "csharp"
},
{
"aliases": [
"frontend"
],
"languageName": null,
"name": "vscode"
},
{
"aliases": [
"js"
],
"languageName": "JavaScript",
"name": "javascript"
},
{
"aliases": [],
"name": "webview"
},
{
"aliases": [],
"languageName": null,
"name": ".NET"
}
]
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@bigjonroberts
Copy link
Author

TODO:

  • remove superfluous items imported from YoLo, just strip it down to any used.
  • make F# and C# sections "the same"
  • Either convert F# to use Task<T> and task {} or add some information about Async being cold/lazy vs Task<T> being hot start.
  • cleanup/remove other non-monad/bind related items from end (move to other notebook?)
  • rename?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment