@@ -17,17 +17,17 @@ trait Monad[F[_]]:
17
17
18
18
// List composition example:
19
19
20
- lazy val listComposition =
21
- for
22
- number <- 0 to 9
23
- letter <- 'A' to 'Z'
20
+ lazy val listComposition =
21
+ for
22
+ number <- 0 to 9
23
+ letter <- 'A' to 'Z'
24
24
yield s " $number$letter"
25
25
26
26
// Which the compiler transforms to:
27
27
28
28
lazy val desugaredListComposition =
29
- (0 to 9 ).flatMap: number =>
30
- ('A' to 'Z' ).map: letter =>
29
+ (0 to 9 ).flatMap: number =>
30
+ ('A' to 'Z' ).map: letter =>
31
31
s " $number$letter"
32
32
33
33
// A functor is simpler and less powerful than a monad:
@@ -37,13 +37,14 @@ trait Functor[F[_]]:
37
37
38
38
// A transformation between two higher-kinded types with the same type parameter:
39
39
40
- trait ~> [F [_], G [_]]:
40
+ trait ~> [F [_], G [_]]:
41
41
def apply [A : Typeable ](f : F [A ]): G [A ]
42
42
43
43
// Free allows us to lift a functor with monadic composition as a data structure:
44
44
45
- sealed trait Free [F [_], A : Typeable ]:
46
- def map [B : Typeable ](f : A => B ): Free [F , B ] = FlatMap (this , (a : A ) => Pure (f(a)))
45
+ sealed trait Free [F [_], A : Typeable ]:
46
+ def map [B : Typeable ](f : A => B ): Free [F , B ] =
47
+ FlatMap (this , (a : A ) => Pure (f(a)))
47
48
def flatMap [B : Typeable ](f : A => Free [F , B ]): Free [F , B ] = FlatMap (this , f)
48
49
49
50
def foldMapAs [G [_]: Monad ](using F ~> G ): G [A ] = this match
@@ -54,8 +55,11 @@ sealed trait Free[F[_], A: Typeable]:
54
55
f(in).foldMapAs[G ]
55
56
case Suspend (s) => summon[F ~> G ](s)
56
57
57
- final case class Pure [F [_], A : Typeable ](value : A ) extends Free [F , A ]
58
- final case class FlatMap [F [_], A : Typeable , B : Typeable ](sub : Free [F , A ], f : A => Free [F , B ]) extends Free [F , B ]
58
+ final case class Pure [F [_], A : Typeable ](value : A ) extends Free [F , A ]
59
+ final case class FlatMap [F [_], A : Typeable , B : Typeable ](
60
+ sub : Free [F , A ],
61
+ f : A => Free [F , B ]
62
+ ) extends Free [F , B ]
59
63
final case class Suspend [F [_], A : Typeable ](s : F [A ]) extends Free [F , A ]
60
64
61
65
// We define a non-monadic type:
@@ -66,65 +70,73 @@ trait LazyCatchable[+A]:
66
70
final class Lazy [A ](value : => A ) extends LazyCatchable [A ]:
67
71
def run (): Either [Catch , A ] = Try (value) match
68
72
case Success (value) => Right (value)
69
- case Failure (e) => Left (Catch (e))
73
+ case Failure (e) => Left (Catch (e))
70
74
71
75
final case class Catch (e : Throwable ) extends LazyCatchable [Nothing ]:
72
76
def run (): Either [Catch , Nothing ] = Left (this )
73
77
74
78
// We can write monadic programs with it:
75
79
76
- lazy val sumProgram : Free [LazyCatchable , Int ] =
77
- for
78
- a <- Suspend (Lazy (1 ))
79
- b <- Suspend (Lazy (2 ))
80
- result <- Pure (a + b)
80
+ lazy val sumProgram : Free [LazyCatchable , Int ] =
81
+ for
82
+ a <- Suspend (Lazy (1 ))
83
+ b <- Suspend (Lazy (2 ))
84
+ result <- Pure (a + b)
81
85
yield result
82
86
83
87
// Which is translated by the compiler to this:
84
88
85
89
lazy val desugaredSumProgram =
86
90
FlatMap (
87
- Suspend (Lazy (1 )),
88
- (num1 : Int ) => FlatMap (
89
- Suspend (Lazy (2 )),
90
- (num2 : Int ) => Pure (num1 + num2)
91
- )
91
+ Suspend (Lazy (1 )),
92
+ (num1 : Int ) =>
93
+ FlatMap (
94
+ Suspend (Lazy (2 )),
95
+ (num2 : Int ) => Pure (num1 + num2)
96
+ )
92
97
)
93
98
94
99
// We provide a ~> to a Future:
95
100
96
101
given LazyCatchable2Future : (LazyCatchable ~> Future ) with
97
102
def apply [A : Typeable ](f : LazyCatchable [A ]): Future [A ] = f match
98
103
case Catch (e) => Future .failed(e)
99
- case lazyValue : Lazy [_] => Future :
100
- lazyValue.run() match
101
- case Left (Catch (e)) => throw e
102
- case Right (value : A @ unchecked) => value
104
+ case lazyValue : Lazy [_] =>
105
+ Future :
106
+ lazyValue.run() match
107
+ case Left (Catch (e)) => throw e
108
+ case Right (value : A @ unchecked) => value
103
109
104
110
// We define a Monad instance for Future:
105
111
106
- given FutureMonad : Monad [Future ] with
107
- def flatMap [A , B ](fa : Future [A ])(f : (A ) => Future [B ]): Future [B ] = fa.flatMap(f)
112
+ given FutureMonad : Monad [Future ] with
113
+ def flatMap [A , B ](fa : Future [A ])(f : (A ) => Future [B ]): Future [B ] =
114
+ fa.flatMap(f)
108
115
109
- def pure [A ](a : A ): Future [A ] = Future (a)
116
+ def pure [A ](a : A ): Future [A ] = Future (a)
110
117
111
118
override def map [A , B ](fa : Future [A ])(f : A => B ): Future [B ] = fa.map(f)
112
119
113
120
// We can then convert our sumProgram to a Future:
114
121
115
- lazy val sumProgramFuture : Future [Int ] = sumProgram.foldMapAs[Future ](using FutureMonad , LazyCatchable2Future ) // Future computes to 3
122
+ lazy val sumProgramFuture : Future [Int ] = sumProgram.foldMapAs[Future ](using
123
+ FutureMonad ,
124
+ LazyCatchable2Future
125
+ ) // Future computes to 3
116
126
117
127
// Let's consider a more advanced workflow DSL:
118
128
119
- enum WorkflowCommand :
129
+ enum WorkflowCommand :
120
130
case FeelInspiredToLearn
121
131
case LikeFriendlyEnvironments
122
132
case WantToHelpPeopleBuildConfidenceCoding
123
133
case JoinBaeldungAsAWriter
124
134
125
135
// We can then define our logic:
126
136
127
- def command [C <: WorkflowCommand ](c : => C ): Free [LazyCatchable , C ] = Suspend (Lazy (c))
137
+ def command [C <: WorkflowCommand ](c : => C ): Free [LazyCatchable , C ] = Suspend (
138
+ Lazy (c)
139
+ )
128
140
129
141
lazy val joinBaeldungWorkflow : Free [LazyCatchable , WorkflowCommand ] =
130
142
for
@@ -137,46 +149,55 @@ lazy val joinBaeldungWorkflow: Free[LazyCatchable, WorkflowCommand] =
137
149
// Then we define a translation to Future:
138
150
139
151
given BaeldungWorkflowInterpreter : (LazyCatchable ~> Future ) with
140
- private def askQuestion (question : String , repeat : Boolean = false ): Boolean =
141
- if repeat then print(s " \n Invalid response: try again (y or n) " )
142
- else print(s " \n $question (y or n) " )
143
-
144
- readChar() match
145
- case 'y' | 'Y' => true
146
- case 'n' | 'N' => false
147
- case _ => askQuestion(question, true )
148
-
149
- private def step [C <: WorkflowCommand ](question : String , command : C , error : String ): Future [C ] = Future :
152
+ private def askQuestion (question : String , repeat : Boolean = false ): Boolean =
153
+ if repeat then print(s " \n Invalid response: try again (y or n) " )
154
+ else print(s " \n $question (y or n) " )
155
+
156
+ readChar() match
157
+ case 'y' | 'Y' => true
158
+ case 'n' | 'N' => false
159
+ case _ => askQuestion(question, true )
160
+
161
+ private def step [C <: WorkflowCommand ](
162
+ question : String ,
163
+ command : C ,
164
+ error : String
165
+ ): Future [C ] = Future :
150
166
if askQuestion(question) then command
151
167
else throw new Exception (error)
152
168
153
169
def apply [A : Typeable ](f : LazyCatchable [A ]): Future [A ] = f match
154
170
case Catch (e) => Future .failed(e)
155
- case lazyCmd : Lazy [_] => lazyCmd.run() match
156
- case Left (Catch (e)) => Future .failed(e)
157
- case Right (command : WorkflowCommand ) =>
158
- command match
159
- case WorkflowCommand .FeelInspiredToLearn =>
160
- step(
161
- question = " Do you feel inspired to learn Scala?" ,
162
- command = command,
163
- error = " Baeldung has tutorials for other technologies too, like Java."
164
- )
165
- case WorkflowCommand .LikeFriendlyEnvironments =>
166
- step(
167
- question = " Do you like friendly environments?" ,
168
- command = command,
169
- error = " Bye."
170
- )
171
- case WorkflowCommand .WantToHelpPeopleBuildConfidenceCoding =>
172
- step(
173
- question = " Do you want to help people build confidence coding?" ,
174
- command = command,
175
- error = " Baeldung tutorials are reliable and informative."
176
- )
177
- case WorkflowCommand .JoinBaeldungAsAWriter => Future .successful(command)
178
- case Right (misc) => Future .successful(misc)
171
+ case lazyCmd : Lazy [_] =>
172
+ lazyCmd.run() match
173
+ case Left (Catch (e)) => Future .failed(e)
174
+ case Right (command : WorkflowCommand ) =>
175
+ command match
176
+ case WorkflowCommand .FeelInspiredToLearn =>
177
+ step(
178
+ question = " Do you feel inspired to learn Scala?" ,
179
+ command = command,
180
+ error =
181
+ " Baeldung has tutorials for other technologies too, like Java."
182
+ )
183
+ case WorkflowCommand .LikeFriendlyEnvironments =>
184
+ step(
185
+ question = " Do you like friendly environments?" ,
186
+ command = command,
187
+ error = " Bye."
188
+ )
189
+ case WorkflowCommand .WantToHelpPeopleBuildConfidenceCoding =>
190
+ step(
191
+ question =
192
+ " Do you want to help people build confidence coding?" ,
193
+ command = command,
194
+ error = " Baeldung tutorials are reliable and informative."
195
+ )
196
+ case WorkflowCommand .JoinBaeldungAsAWriter =>
197
+ Future .successful(command)
198
+ case Right (misc) => Future .successful(misc)
179
199
180
200
// The translation is then very simple and intuitive:
181
201
182
- lazy val joinBaeldung : Future [WorkflowCommand ] = joinBaeldungWorkflow.foldMapAs[Future ](using FutureMonad , BaeldungWorkflowInterpreter )
202
+ lazy val joinBaeldung : Future [WorkflowCommand ] = joinBaeldungWorkflow
203
+ .foldMapAs[Future ](using FutureMonad , BaeldungWorkflowInterpreter )
0 commit comments