Skip to content

Commit df88896

Browse files
committed
port examples to scala 2
1 parent 9bd1fd1 commit df88896

File tree

1 file changed

+127
-20
lines changed

1 file changed

+127
-20
lines changed

_overviews/scala3-book/domain-modeling-fp.md

Lines changed: 127 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,48 @@ You’ll see how to implement the “data” portion of the Scala/FP model, and
5151

5252
In Scala, describing the data model of a programming problem is simple:
5353

54-
- If you want to model data with different alternatives, use the `enum` construct
54+
- If you want to model data with different alternatives, use the `enum` construct, (or `case object` in Scala 2).
5555
- If you only want to group things (or need more fine-grained control) use `case` classes
5656

5757
### Describing Alternatives
5858

59-
Data that simply consists of different alternatives, like crust size, crust type, and toppings, is concisely modeled with the Scala 3 `enum` construct:
59+
Data that simply consists of different alternatives, like crust size, crust type, and toppings, is precisely modelled
60+
in Scala by an enumeration.
6061

61-
{% tabs data_1 %}
62-
{% tab 'Scala 3 Only' for=data_1 %}
62+
{% tabs data_1 class=tabs-scala-version %}
63+
{% tab 'Scala 2' for=data_1 %}
64+
65+
In Scala 2 enumerations are expressed with a combination of a `sealed class` and several `case object` that extend the class:
66+
67+
```scala
68+
sealed abstract class CrustSize
69+
object CrustSize {
70+
case object Small extends CrustSize
71+
case object Medium extends CrustSize
72+
case object Large extends CrustSize
73+
}
74+
75+
sealed abstract class CrustType
76+
object CrustType {
77+
case object Thin extends CrustType
78+
case object Thick extends CrustType
79+
case object Regular extends CrustType
80+
}
81+
82+
sealed abstract class Topping
83+
object Topping {
84+
case object Cheese extends Topping
85+
case object Pepperoni extends Topping
86+
case object BlackOlives extends Topping
87+
case object GreenOlives extends Topping
88+
case object Onions extends Topping
89+
}
90+
```
91+
92+
{% endtab %}
93+
{% tab 'Scala 3' for=data_1 %}
94+
95+
In Scala 3 enumerations are concisely expressed with the `enum` construct:
6396

6497
```scala
6598
enum CrustSize:
@@ -80,10 +113,25 @@ enum Topping:
80113
### Describing Compound Data
81114

82115
A pizza can be thought of as a _compound_ container of the different attributes above.
83-
We can use a `case` class to describe that a `Pizza` consists of a `crustSize`, `crustType`, and potentially multiple `Topping`s:
116+
We can use a `case` class to describe that a `Pizza` consists of a `crustSize`, `crustType`, and potentially multiple `toppings`:
84117

85-
{% tabs data_2 %}
86-
{% tab 'Scala 3 Only' for=data_2 %}
118+
{% tabs data_2 class=tabs-scala-version %}
119+
{% tab 'Scala 2' for=data_2 %}
120+
121+
```scala
122+
import CrustSize._
123+
import CrustType._
124+
import Topping._
125+
126+
case class Pizza(
127+
crustSize: CrustSize,
128+
crustType: CrustType,
129+
toppings: Seq[Topping]
130+
)
131+
```
132+
133+
{% endtab %}
134+
{% tab 'Scala 3' for=data_2 %}
87135

88136
```scala
89137
import CrustSize.*
@@ -288,8 +336,7 @@ Mine (Alvin, now modified, from fp-pure-functions.md):
288336
## How to Organize Functionality
289337

290338
When implementing the `pizzaPrice` function above, we did not say _where_ we would define it.
291-
In Scala 3, it would be perfectly valid to define it on the toplevel of your file.
292-
However, the language gives us many great tools to organize our logic in different namespaces and modules.
339+
Scala gives you many great tools to organize your logic in different namespaces and modules.
293340

294341
There are several different ways to implement and organize behaviors:
295342

@@ -308,8 +355,39 @@ A first approach is to define the behavior---the functions---in a companion obje
308355
309356
With this approach, in addition to the enumeration or case class you also define an equally named companion object that contains the behavior.
310357

311-
{% tabs org_1 %}
312-
{% tab 'Scala 3 Only' for=org_1 %}
358+
{% tabs org_1 class=tabs-scala-version %}
359+
{% tab 'Scala 2' for=org_1 %}
360+
361+
```scala
362+
case class Pizza(
363+
crustSize: CrustSize,
364+
crustType: CrustType,
365+
toppings: Seq[Topping]
366+
)
367+
368+
// the companion object of case class Pizza
369+
object Pizza {
370+
// the implementation of `pizzaPrice` from above
371+
def price(p: Pizza): Double = ...
372+
}
373+
374+
sealed abstract class Topping
375+
376+
// the companion object of enumeration Topping
377+
object Topping {
378+
case object Cheese extends Topping
379+
case object Pepperoni extends Topping
380+
case object BlackOlives extends Topping
381+
case object GreenOlives extends Topping
382+
case object Onions extends Topping
383+
384+
// the implementation of `toppingPrice` above
385+
def price(t: Topping): Double = ...
386+
}
387+
```
388+
389+
{% endtab %}
390+
{% tab 'Scala 3' for=org_1 %}
313391

314392
```scala
315393
case class Pizza(
@@ -329,9 +407,7 @@ enum Topping:
329407
// the companion object of enumeration Topping
330408
object Topping:
331409
// the implementation of `toppingPrice` above
332-
def price(t: Topping): Double = t match
333-
case Cheese | Onions => 0.5
334-
case Pepperoni | BlackOlives | GreenOlives => 0.75
410+
def price(t: Topping): Double = ...
335411
```
336412

337413
{% endtab %}
@@ -643,8 +719,8 @@ This can have multiple advantages:
643719

644720
Let us revisit our example once more.
645721

646-
{% tabs module_7 %}
647-
{% tab 'Scala 3 Only' for=module_7 %}
722+
{% tabs module_7 class=tabs-scala-version %}
723+
{% tab 'Scala 2' for=module_7 %}
648724

649725
```scala
650726
case class Pizza(
@@ -653,7 +729,7 @@ case class Pizza(
653729
toppings: Seq[Topping]
654730
)
655731

656-
extension (p: Pizza)
732+
implicit class PizzaOps(p: Pizza) {
657733
def price: Double =
658734
pizzaPrice(p) // implementation from above
659735

@@ -668,15 +744,46 @@ extension (p: Pizza)
668744

669745
def updateCrustType(ct: CrustType): Pizza =
670746
p.copy(crustType = ct)
747+
}
671748
```
749+
In the above code, we define the different methods on pizzas as methods in an _implicit class_.
750+
With `implicit class PizzaOps(p: Pizza)` then wherever `PizzaOps` is imported its methods will be available on
751+
instances of `Pizza`. The reciever in this case is `p`.
672752

673753
{% endtab %}
674-
{% endtabs %}
754+
{% tab 'Scala 3' for=module_7 %}
755+
756+
```scala
757+
case class Pizza(
758+
crustSize: CrustSize,
759+
crustType: CrustType,
760+
toppings: Seq[Topping]
761+
)
762+
763+
extension (p: Pizza)
764+
def price: Double =
765+
pizzaPrice(p) // implementation from above
766+
767+
def addTopping(t: Topping): Pizza =
768+
p.copy(toppings = p.toppings :+ t)
769+
770+
def removeAllToppings: Pizza =
771+
p.copy(toppings = Seq.empty)
675772

773+
def updateCrustSize(cs: CrustSize): Pizza =
774+
p.copy(crustSize = cs)
775+
776+
def updateCrustType(ct: CrustType): Pizza =
777+
p.copy(crustType = ct)
778+
```
676779
In the above code, we define the different methods on pizzas as _extension methods_.
677-
With `extension (p: Pizza)` we say that we want to make the methods available on instances of `Pizza` and refer to the instance we extend as `p` in the following.
780+
With `extension (p: Pizza)` we say that we want to make the methods available on instances of `Pizza`. The reciever
781+
in this case is `p`.
782+
783+
{% endtab %}
784+
{% endtabs %}
678785

679-
This way, we can obtain the same API as before
786+
Using our extension methods, we can obtain the same API as before:
680787

681788
{% tabs module_8 %}
682789
{% tab 'Scala 2 and 3' for=module_8 %}

0 commit comments

Comments
 (0)