Skip to content

Commit e8fdd1b

Browse files
committed
Scala 3 Braceless Syntax cont.
1 parent 75330d5 commit e8fdd1b

File tree

19 files changed

+100
-183
lines changed

19 files changed

+100
-183
lines changed

src/pages/applicatives/applicative.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,14 @@ introduced in Chapter [@sec:monads].
2424
Here's a simplified definition in code:
2525

2626
```scala
27-
trait Apply[F[_]] extends Semigroupal[F] with Functor[F] {
27+
trait Apply[F[_]] extends Semigroupal[F] with Functor[F]:
2828
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
2929

3030
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
3131
ap(map(fa)(a => (b: B) => (a, b)))(fb)
32-
}
3332

34-
trait Applicative[F[_]] extends Apply[F] {
33+
trait Applicative[F[_]] extends Apply[F]:
3534
def pure[A](a: A): F[A]
36-
}
3735
```
3836

3937
Breaking this down, the `ap` method applies a parameter `fa`

src/pages/applicatives/parallel.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,12 @@ Let's dig into how `Parallel` works.
8888
The definition below is the core of `Parallel`.
8989

9090
```scala
91-
trait Parallel[M[_]] {
91+
trait Parallel[M[_]]:
9292
type F[_]
9393

9494
def applicative: Applicative[F]
9595
def monad: Monad[M]
9696
def parallel: ~>[M, F]
97-
}
9897
```
9998

10099
This tells us if there is a `Parallel` instance for some type constructor `M` then:

src/pages/applicatives/semigroupal.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ a `Semigroupal[F]` allows us to combine them to form an `F[(A, B)]`.
77
Its definition in Cats is:
88

99
```scala
10-
trait Semigroupal[F[_]] {
10+
trait Semigroupal[F[_]]:
1111
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
12-
}
1312
```
1413

1514
As we discussed at the beginning of this chapter,

src/pages/case-studies/crdt/abstraction.md

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,25 @@ represent the key and value types of the map abstraction.
2727
```scala mdoc:reset-object:invisible
2828
import cats.kernel.CommutativeMonoid
2929

30-
trait BoundedSemiLattice[A] extends CommutativeMonoid[A] {
30+
trait BoundedSemiLattice[A] extends CommutativeMonoid[A]:
3131
def combine(a1: A, a2: A): A
3232
def empty: A
33-
}
3433

35-
object BoundedSemiLattice {
36-
given intInstance: BoundedSemiLattice[Int] with
37-
def combine(a1: Int, a2: Int): Int =
38-
a1 max a2
34+
given intInstance: BoundedSemiLattice[Int] with
35+
def combine(a1: Int, a2: Int): Int =
36+
a1 max a2
3937

40-
val empty: Int =
41-
0
38+
val empty: Int = 0
4239

43-
given setInstance[A]: BoundedSemiLattice[Set[A]] with
44-
def combine(a1: Set[A], a2: Set[A]): Set[A] =
45-
a1 union a2
40+
given setInstance[A]: BoundedSemiLattice[Set[A]] with
41+
def combine(a1: Set[A], a2: Set[A]): Set[A] =
42+
a1 union a2
4643

47-
val empty: Set[A] =
48-
Set.empty[A]
49-
}
44+
val empty: Set[A] =
45+
Set.empty[A]
5046
```
5147
```scala mdoc:silent
52-
trait GCounter[F[_,_],K, V] {
48+
trait GCounter[F[_,_],K, V]:
5349
def increment(f: F[K, V])(k: K, v: V)
5450
(using m: CommutativeMonoid[V]): F[K, V]
5551

@@ -58,13 +54,11 @@ trait GCounter[F[_,_],K, V] {
5854

5955
def total(f: F[K, V])
6056
(using m: CommutativeMonoid[V]): V
61-
}
6257

63-
object GCounter {
58+
object GCounter:
6459
def apply[F[_,_], K, V]
6560
(using counter: GCounter[F, K, V]) =
6661
counter
67-
}
6862
```
6963

7064
Try defining an instance of this type class for `Map`.
@@ -114,7 +108,7 @@ val counter = GCounter[Map, String, Int]
114108

115109
```scala mdoc
116110
val merged = counter.merge(g1, g2)
117-
val total = counter.total(merged)
111+
val total = counter.total(merged)(using intInstance)
118112
```
119113

120114
The implementation strategy
@@ -133,7 +127,7 @@ for any type that has a `KeyValueStore` instance.
133127
Here's the code for such a type class:
134128

135129
```scala mdoc:silent
136-
trait KeyValueStore[F[_,_]] {
130+
trait KeyValueStore[F[_,_]]:
137131
def put[K, V](f: F[K, V])(k: K, v: V): F[K, V]
138132

139133
def get[K, V](f: F[K, V])(k: K): Option[V]
@@ -142,7 +136,6 @@ trait KeyValueStore[F[_,_]] {
142136
get(f)(k).getOrElse(default)
143137

144138
def values[K, V](f: F[K, V]): List[V]
145-
}
146139
```
147140

148141
Implement your own instance for `Map`.
@@ -197,9 +190,7 @@ instances of `KeyValueStore` and `CommutativeMonoid`
197190
using an `implicit def`:
198191

199192
```scala mdoc:silent
200-
implicit def gcounterInstance[F[_,_], K, V]
201-
(using kvs: KeyValueStore[F], km: CommutativeMonoid[F[K, V]]): GCounter[F, K, V] =
202-
new GCounter[F, K, V] {
193+
given gcounterInstance[F[_,_], K, V](using kvs: KeyValueStore[F], km: CommutativeMonoid[F[K, V]]): GCounter[F, K, V] with
203194
def increment(f: F[K, V])(key: K, value: V)
204195
(using m: CommutativeMonoid[V]): F[K, V] = {
205196
val total = f.getOrElse(key, m.empty) |+| value
@@ -212,7 +203,6 @@ implicit def gcounterInstance[F[_,_], K, V]
212203

213204
def total(f: F[K, V])(using m: CommutativeMonoid[V]): V =
214205
f.values.combineAll
215-
}
216206
```
217207

218208
The complete code for this case study is quite long,

src/pages/case-studies/crdt/g-counter.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ We can implement a GCounter with the following interface,
100100
where we represent machine IDs as `Strings`.
101101

102102
```scala mdoc:reset-object:silent
103-
final case class GCounter(counters: Map[String, Int]) {
103+
final case class GCounter(counters: Map[String, Int]):
104104
def increment(machine: String, amount: Int) =
105105
???
106106

@@ -109,7 +109,6 @@ final case class GCounter(counters: Map[String, Int]) {
109109

110110
def total: Int =
111111
???
112-
}
113112
```
114113

115114
Finish the implementation!
@@ -119,7 +118,7 @@ Hopefully the description above was clear enough that
119118
you can get to an implementation like the one below.
120119

121120
```scala mdoc:silent:reset-object
122-
final case class GCounter(counters: Map[String, Int]) {
121+
final case class GCounter(counters: Map[String, Int]):
123122
def increment(machine: String, amount: Int) = {
124123
val value = amount + counters.getOrElse(machine, 0)
125124
GCounter(counters + (machine -> value))
@@ -131,8 +130,6 @@ final case class GCounter(counters: Map[String, Int]) {
131130
k -> (v max that.counters.getOrElse(k, 0))
132131
})
133132

134-
def total: Int =
135-
counters.values.sum
136-
}
133+
def total: Int = counters.values.sum
137134
```
138135
</div>

src/pages/case-studies/crdt/generalisation.md

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,9 @@ our own `BoundedSemiLattice` type class.
113113
```scala mdoc:silent
114114
import cats.kernel.CommutativeMonoid
115115

116-
trait BoundedSemiLattice[A] extends CommutativeMonoid[A] {
116+
trait BoundedSemiLattice[A] extends CommutativeMonoid[A]:
117117
def combine(a1: A, a2: A): A
118118
def empty: A
119-
}
120119
```
121120

122121
In the implementation above,
@@ -146,28 +145,26 @@ import cats.kernel.CommutativeMonoid
146145
```
147146

148147
```scala mdoc:silent
149-
object wrapper {
150-
trait BoundedSemiLattice[A] extends CommutativeMonoid[A] {
148+
object wrapper:
149+
trait BoundedSemiLattice[A] extends CommutativeMonoid[A]:
151150
def combine(a1: A, a2: A): A
152151
def empty: A
153-
}
154152

155-
object BoundedSemiLattice {
156-
given intInstance: BoundedSemiLattice[Int] with
157-
def combine(a1: Int, a2: Int): Int =
158-
a1 max a2
153+
given intInstance: BoundedSemiLattice[Int] with
154+
def combine(a1: Int, a2: Int): Int =
155+
a1 max a2
159156

160-
val empty: Int =
161-
0
157+
val empty: Int =
158+
0
162159

163-
given setInstance[A]: BoundedSemiLattice[Set[A]] with
164-
def combine(a1: Set[A], a2: Set[A]): Set[A] =
165-
a1 union a2
160+
given setInstance[A]: BoundedSemiLattice[Set[A]] with
161+
def combine(a1: Set[A], a2: Set[A]): Set[A] =
162+
a1 union a2
166163

167-
val empty: Set[A] =
168-
Set.empty[A]
169-
}
170-
}; import wrapper.*
164+
val empty: Set[A] =
165+
Set.empty[A]
166+
167+
import wrapper.*
171168
```
172169
</div>
173170

@@ -197,7 +194,7 @@ import cats.instances.map.* // for Monoid
197194
import cats.syntax.semigroup.* // for |+|
198195
import cats.syntax.foldable.* // for combineAll
199196

200-
final case class GCounter[A](counters: Map[String,A]) {
197+
final case class GCounter[A](counters: Map[String,A]):
201198
def increment(machine: String, amount: A)
202199
(using m: CommutativeMonoid[A]): GCounter[A] = {
203200
val value = amount |+| counters.getOrElse(machine, m.empty)
@@ -210,7 +207,6 @@ final case class GCounter[A](counters: Map[String,A]) {
210207

211208
def total(using m: CommutativeMonoid[A]): A =
212209
this.counters.values.toList.combineAll
213-
}
214210
```
215211
</div>
216212

src/pages/case-studies/parser/applicative.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,12 @@ Let's implement an instance of Scalaz's `Applicative` type class for our `Parser
143143
Define a typeclass instance of `Applicative` for `Parser`. You must implement the following trait:
144144

145145
~~~ scala
146-
Applicative[Parser] {
146+
Applicative[Parser] with
147147
def point[A](a: => A): Parser[A] =
148148
???
149149

150150
def ap[A, B](fa: => Parser[A])(f: => Parser[A => B]): Parser[B] =
151151
???
152-
}
153152
~~~
154153

155154
Hints:
@@ -166,25 +165,24 @@ The usual place to define typeclass instances is as implicit elements on the com
166165
val identity: Parser[Unit] =
167166
Parser { input => Success(Unit, input) }
168167

169-
implicit object applicativeInstance extends Applicative[Parser] {
168+
given applicativeInstance: Applicative[Parser] with
170169
def point[A](a: => A): Parser[A] =
171170
identity map (_ => a)
172171

173172
def ap[A, B](fa: => Parser[A])(f: => Parser[A => B]): Parser[B] =
174173
Parser { input =>
175-
f.parse(input) match {
174+
f.parse(input) match
176175
case fail @ Failure(_) =>
177176
fail
178177
case Success(aToB, remainder) =>
179-
fa.parse(remainder) match {
178+
fa.parse(remainder) match
180179
case fail @ Failure(_) =>
181180
fail
182181
case Success(a, remainder1) =>
183182
Success(aToB(a), remainder1)
184-
}
185-
}
183+
end match
184+
end match
186185
}
187-
}
188186
~~~
189187

190188
Checkout the `parser-applicative` tag to see the full code and tests.

src/pages/case-studies/parser/error-handling.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,8 @@ Implement a parser for digits using the regular expression `"[0-9]"` to match a
129129
~~~ scala
130130
package underscore.parser
131131

132-
object NumericParser {
133-
132+
object NumericParser:
134133
val digits = Parser.regex("[0-9]").+
135-
136-
}
137134
~~~
138135
</div>
139136

@@ -184,14 +181,11 @@ If `add` was a normal method we'ld only print `"Hi"` once.
184181
If we make the parameter of `~` and `|` call-by-name, our `Parser` will work. Try it and you'll see another issue---the way the grammar is written we'll stop after parsing the first number. (Try `expression.parse("123+456")` and you'll see.) The solution is to rewrite the grammar so we look for compound expressions first and we proceed left-to-right.
185182

186183
~~~ scala
187-
object NumericParser {
188-
184+
object NumericParser:
189185
val digits = Parser.regex("[0-9]").+
190186

191187
def expression: Parser =
192188
(digits ~ Parser.string("+") ~ expression) | (digits ~ Parser.string("-") ~ expression) | digits
193-
194-
}
195189
~~~
196190

197191
The code is tagged with `parser-numeric-expression`.

src/pages/case-studies/parser/intro.md

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,14 @@ package underscore.parser
3232

3333
import scala.annotation.tailrec
3434

35-
case class ParseResult(result: String, remainder: String) {
36-
35+
case class ParseResult(result: String, remainder: String):
3736
def failed: Boolean =
3837
result.isEmpty
3938

4039
def success: Boolean =
4140
!failed
4241

43-
}
44-
45-
case class Parser(parse: String => ParseResult) {
46-
42+
case class Parser(parse: String => ParseResult):
4743
def ~(next: Parser): Parser = ???
4844

4945
def `*`: Parser =
@@ -60,19 +56,14 @@ case class Parser(parse: String => ParseResult) {
6056
loop("", input)
6157
}
6258

63-
}
64-
65-
object Parser {
66-
59+
object Parser:
6760
def string(literal: String): Parser =
6861
Parser { input =>
6962
if(input.startsWith(literal))
7063
ParseResult(literal, input.drop(literal.size))
7164
else
7265
ParseResult("", input)
7366
}
74-
75-
}
7667
~~~
7768

7869
What does the code do? The first thing is to look at what a `Parser` is. It is basically a wrapper around a function `String => ParseResult`. The `String` parameter is the input to parse, and returned is the result of parsing that `String`.

0 commit comments

Comments
 (0)