<Query Kind="FSharpProgram" />

let unit t = [t]

let flat' m2 = List.concat m2
let flat m2 =
    let mutable r = []
    for m in m2 do
        for t in m do
            r <- r @ [t]
    r
    
let map' h m = List.map h m
let map h m =
    let mutable r = []
    for t in m do
        r <- r @ [h t]
    r

let bind' g m = List.collect g m
let bind g m =
    let mutable r = []
    for t in m do
        for u in g t do
            r <- r @ [u]
    r

let flat'' m2 = bind id m2
let map'' h = bind (h >> unit)
let bind'' g = map g >> flat

let h t = t+1
let f t = [t]
let f' t = [t+1]
let g t = [t; t+1]

printfn "unit: %A => %A" 3 (unit 3)
printfn "flat: %A => %A, %A => %A, %A => %A" [[3]; []; [5;7]] (flat [[3]; []; [5;7]]) [[]] (flat [[]]) [] (flat [])
printfn "map h: %A => %A, %A => %A" [3;5;7] (map h [3;5;7]) [] (map h [])
printfn "map g: %A => %A, %A => %A" [3;5;7] (map g [3;5;7]) [] (map g [])
printfn "bind g: %A => %A, %A => %A" [3;5;7] (bind g [3;5;7]) [] (bind g [])

let (>>=) m g = m |> bind g
let (>=>) f g = f >> bind g

printfn "kleisli: %A => %A, %A => %A" [3;5;7] ([3;5;7] >>= (f >=> g)) [] ([] >>= (f >=> g))

type ListBuilder () =
    member this.Bind (m, g) = bind g m  // let! do!
    member this.ReturnFrom (m) = m      // return!
    member this.Return (t) = unit t     // return

let mu = ListBuilder ()  // new

let x m = mu {
    let! t = m
    let! u = f t
    return! g u
}

printfn "sugar: %A => %A, %A => %A" [3;5;7] (x [3;5;7]) [] (x [])

let y m =
    mu.Bind (m,
        fun t -> 
            mu.Bind (f t,
                fun u -> mu.ReturnFrom (g u)))

printfn "builder: %A => %A, %A => %A" [3;5;7] (y [3;5;7]) [] (y [])
//