Created
November 15, 2021 12:46
-
-
Save SchlenkR/5830edf1c934561a0f7f82101e4b86b0 to your computer and use it in GitHub Desktop.
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
module ListComprehensions | |
// sehr einfach! | |
let result = | |
let a = 10 | |
let b = 20 | |
let c = 30 | |
a + b + c | |
// könnte man auch so schreiben: | |
let result1 = | |
let a = 10 in | |
let b = 20 in | |
let c = 30 in | |
a + b + c | |
// let-desugarization | |
let result3 = | |
(fun a -> | |
fun b -> | |
fun c -> | |
a + b + c) 10 20 30 | |
// ...mit einer "bindValue"-Funktion: | |
let bindValue x f = f x | |
let result4 = | |
bindValue 10 (fun a -> | |
bindValue 20 (fun b -> | |
bindValue 30 (fun c -> | |
a + b + c))) | |
// dedent - und es sieht schon ziemlich aus wie Bsp. 1, | |
// nur dass die Ausdrücke, dir gebunden werden, rechts vom Bezeichner stehen: | |
// let a = 10 in [exp] | |
// 10 |> fun a -> [exp] | |
let result5 = | |
bindValue 10 (fun a -> | |
bindValue 20 (fun b -> | |
bindValue 30 (fun c -> | |
a + b + c | |
))) | |
// Und in der Domäne der Listen - auch ähnlich: | |
let result6 = [ | |
for a in [0..2] do | |
for b in [3..5] do | |
for c in [6..8] do | |
yield a + b + c // 'yield' einfach mal ignorieren | |
] | |
// sieht fast genau so aus wie "let", nur mit einem kleinen | |
// Unterschied in den Typen der gebundenen Ausdrücke: | |
let usingLet : int = | |
let (a: int) = ( 10 : int ) in a | |
let usingFor : int list = | |
[ for (a: int) in ([0..2] : int list) do a ] | |
// Wie genau funktioniert denn diese Comprehension? | |
// Versuchen wir es einfach mal anstatt mit "for" mit "let" | |
// - das würde nicht kompilieren - obwohl es täuschend ähnlich ist: | |
(* | |
let result7 = | |
let a = [0..2] in // wir weisen die Liste 'a' zu - aber wir | |
let b = [3..5] in // wollen eigentlich ein einzelnes Element der Liste | |
let c = [6..8] in // a zuweisen! | |
a + b + c | |
*) | |
// Schreiben wir mal wieder um mit Lambdas - geht natürlichz | |
// immer noch nicht, weil äquivalent mit result7: | |
(* | |
let result8 = | |
bindValue [0..2] (fun a -> | |
bindValue [3..5] (fun b -> | |
bindValue [6..8] (fun c -> | |
a + b + c))) | |
*) | |
// was wäre denn, wenn wir anstatt 'bindValue'- eine 'bindList'-Funktion | |
// schreiben? Wie wäre denn der Typ dieser Funktion? | |
// bindValue: (x: 'a) -> (f: 'a -> 'b) -> 'b | |
// bindList: (x: list<'a>) -> (f: 'a -> 'b list) -> 'b list | |
let bindList x f = | |
let rec processRestOfList rest = | |
match rest with | |
| head :: tail -> List.append (f head) (processRestOfList tail) | |
| [] -> [] | |
processRestOfList x | |
// ...dann sieht das ganze nun so aus: | |
(* | |
let result9 = | |
bindList [0..2] (fun a -> | |
bindList [3..5] (fun b -> | |
bindList [6..8] (fun c -> | |
a + b + c))) | |
*) | |
// ...result9 geht aber immer noch nicht, denn: | |
// die innere Funktion muss ja ('a -> 'b list) sein. | |
// fun c -> ... muss also eine Liste zurückgeben und nicht nur | |
// einen einfachen Wert. Kein Problem - nennen wir die Funktion, | |
// die uns einen einfachen Wert in eine Liste einpackt einfach "returnList": | |
let returnList x = [x] | |
let result9 = | |
bindList [0..2] (fun a -> | |
bindList [3..5] (fun b -> | |
bindList [6..8] (fun c -> | |
returnList(a + b + c)))) | |
// ...bisschen Formatierung: | |
let result10 = | |
bindList [0..2] (fun a -> | |
bindList [3..5] (fun b -> | |
bindList [6..8] (fun c -> | |
returnList (a + b + c)))) | |
// ...aus bindList machen wir einen Infix-Operator, damit die | |
// Klammern entfallen | |
let (>>=) = bindList | |
let result11 = | |
[0..2] >>= fun a -> | |
[3..5] >>= fun b -> | |
[6..8] >>= fun c -> | |
returnList (a + b + c) | |
// hier nochmal das Beispiel mit "for" | |
// und mit "let" (was leider nicht funktioniert hatte): | |
let withBind = | |
[0..2] >>= fun a -> | |
[3..5] >>= fun b -> | |
[6..8] >>= fun c -> | |
returnList (a + b + c) | |
let withFor = [ | |
for a in [0..2] do | |
for b in [3..5] do | |
for c in [6..8] do | |
yield a + b + c | |
] | |
(* | |
let withLet = | |
let a = [0..2] in | |
let b = [3..5] in | |
let c = [6..8] in | |
a + b + c | |
*) | |
// ...wenn wir jetzt noch eine Sprache hätten, die das Konzept mit | |
// 'bindList' und 'returnList' verstehen würde, könnten wir uns ganz | |
// einfach selbst sowas wie Listcomprehensions bauen! | |
// Und das haben wir, indem wir eine zustandslose Klasse definieren, | |
// die die Member 'Bind' und 'Return hat: | |
type JuliusListComputation() = | |
member this.Bind(m, f) = bindList m f | |
member this.Return(v) = returnList v | |
let jlc = JuliusListComputation() | |
// jetzt können wir diese Syntax nutzen (mit let!) | |
let finalResultWithLetBang = jlc { | |
let! a = [0..2] in | |
let! b = [3..5] in | |
let! c = [6..8] in | |
return a + b + c | |
} | |
// weil 'for' eigentlich auch nichts anderes ist, können wir auch | |
// 'for' in unserem Kontext aktivieren. Die Methoden heißen dann | |
// nicht 'Bind' und 'Return', sondern 'For' und 'Yield' - sonst | |
// ist alles gleich: | |
type JuliusListComputationWithFor() = | |
member this.For(m, f) = bindList m f | |
member this.Yield(v) = returnList v | |
let jlcx = JuliusListComputationWithFor() | |
let finalResultWithFor = jlcx { | |
for a in [0..2] do | |
for b in [3..5] do | |
for c in [6..8] do | |
yield a + b + c | |
} | |
printfn $"Final result: {finalResultWithFor}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment