Skip to content

Instantly share code, notes, and snippets.

@SchlenkR
Created November 15, 2021 12:46
Show Gist options
  • Save SchlenkR/5830edf1c934561a0f7f82101e4b86b0 to your computer and use it in GitHub Desktop.
Save SchlenkR/5830edf1c934561a0f7f82101e4b86b0 to your computer and use it in GitHub Desktop.
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