Last active
February 15, 2021 15:10
-
-
Save emcake/d6456fb45a0995b4afef00628a4557ff to your computer and use it in GitHub Desktop.
Applicative builders in F# by abusing For and IsLikeZip
This file contains 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
type ListBuilder() = | |
/// map | |
member x.For(l,f) = | |
List.map f l | |
/// id | |
member x.Yield(v) = | |
v | |
/// merge + map | |
[<CustomOperation("zip", IsLikeZip = true)>] | |
member x.Zip(l1, l2, f) = | |
List.map2 f l1 l2 | |
let list = ListBuilder() | |
list { | |
for x in [1;2;3] do | |
zip y in ["a";"b";"c"] | |
zip z in [true; false; true] | |
zip w in [4;5;6] | |
yield (x+w),y,z | |
} | |
//also works for not-lists... | |
type 'a SuperValue = | |
{ | |
Sources : string list | |
Value : 'a | |
} | |
module SuperValue = | |
let make<'a> (v:'a) : 'a SuperValue = | |
{ | |
Sources = [ sprintf "%+A" v ] | |
Value = v | |
} | |
let map f sp = | |
{ | |
Sources = sp.Sources | |
Value = sp.Value |> f | |
} | |
let apply (xSp) (fSp) = | |
{ | |
Sources = fSp.Sources @ xSp.Sources | |
Value = fSp.Value xSp.Value | |
} | |
let bind f xSp = | |
xSp.Value |> f | |
let x = SuperValue.make 1 // : int SuperValue = {Sources = ["1"]; Value = 1;} | |
let y = SuperValue.make 2 // : int SuperValue = {Sources = ["2"]; Value = 2;} | |
type SuperValueBuilder () = | |
// id | |
member __.Yield (f : 'a) = f | |
// f $ a <*> b | |
[< CustomOperation("also", IsLikeZip = true)>] | |
member __.Merge((a:'a SuperValue),(b:'b SuperValue),f : ('a -> 'b -> 'c)) = | |
SuperValue.make f | |
|> SuperValue.apply a | |
|> SuperValue.apply b | |
/// f $ a | |
member __.For(a,f) = SuperValue.map f a | |
let supervalue = SuperValueBuilder() | |
let z = | |
supervalue { | |
for x in x do | |
also y in y | |
yield (float x / float y) | |
} | |
// : float SuperValue = {Sources = ["<fun:z@78-4>"; "1"; "2"]; Value = 0.5;} | |
// also plays nice with binds.. | |
type SuperValueBuilder with | |
member __.Return x = SuperValue.make x | |
member __.ReturnFrom x = x | |
member __.Bind(sv, f) = SuperValue.bind f sv | |
let divideOrAdd x y div = | |
let divide = | |
supervalue { | |
for x in x do | |
also y in y | |
yield (float x / float y) | |
} | |
supervalue { | |
let! d = div | |
if d then return! divide | |
else | |
return! supervalue { | |
for x in x do | |
also y in y | |
yield (float x + float y) | |
} | |
} | |
divideOrAdd x y (SuperValue.make true) // : float SuperValue = {Sources = ["<fun:divide@96-1>"; "1"; "2"]; Value = 0.5;} | |
divideOrAdd x y (SuperValue.make false) // : float SuperValue = {Sources = ["<fun:divideOrAdd@106-1>"; "1"; "2"]; Value = 3.0;} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment