You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -51,15 +51,48 @@ You’ll see how to implement the “data” portion of the Scala/FP model, and
51
51
52
52
In Scala, describing the data model of a programming problem is simple:
53
53
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).
55
55
- If you only want to group things (or need more fine-grained control) use `case` classes
56
56
57
57
### Describing Alternatives
58
58
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.
60
61
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
+
sealedabstractclassCrustSize
69
+
objectCrustSize {
70
+
caseobjectSmallextendsCrustSize
71
+
caseobjectMediumextendsCrustSize
72
+
caseobjectLargeextendsCrustSize
73
+
}
74
+
75
+
sealedabstractclassCrustType
76
+
objectCrustType {
77
+
caseobjectThinextendsCrustType
78
+
caseobjectThickextendsCrustType
79
+
caseobjectRegularextendsCrustType
80
+
}
81
+
82
+
sealedabstractclassTopping
83
+
objectTopping {
84
+
caseobjectCheeseextendsTopping
85
+
caseobjectPepperoniextendsTopping
86
+
caseobjectBlackOlivesextendsTopping
87
+
caseobjectGreenOlivesextendsTopping
88
+
caseobjectOnionsextendsTopping
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:
63
96
64
97
```scala
65
98
enumCrustSize:
@@ -80,10 +113,25 @@ enum Topping:
80
113
### Describing Compound Data
81
114
82
115
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`:
84
117
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
+
importCrustSize._
123
+
importCrustType._
124
+
importTopping._
125
+
126
+
caseclassPizza(
127
+
crustSize: CrustSize,
128
+
crustType: CrustType,
129
+
toppings: Seq[Topping]
130
+
)
131
+
```
132
+
133
+
{% endtab %}
134
+
{% tab 'Scala 3' for=data_2 %}
87
135
88
136
```scala
89
137
importCrustSize.*
@@ -288,8 +336,7 @@ Mine (Alvin, now modified, from fp-pure-functions.md):
288
336
## How to Organize Functionality
289
337
290
338
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.
293
340
294
341
There are several different ways to implement and organize behaviors:
295
342
@@ -308,8 +355,39 @@ A first approach is to define the behavior---the functions---in a companion obje
308
355
309
356
With this approach, in addition to the enumeration or case class you also define an equally named companion object that contains the behavior.
310
357
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
+
caseclassPizza(
363
+
crustSize: CrustSize,
364
+
crustType: CrustType,
365
+
toppings: Seq[Topping]
366
+
)
367
+
368
+
// the companion object of case class Pizza
369
+
objectPizza {
370
+
// the implementation of `pizzaPrice` from above
371
+
defprice(p: Pizza):Double= ...
372
+
}
373
+
374
+
sealedabstractclassTopping
375
+
376
+
// the companion object of enumeration Topping
377
+
objectTopping {
378
+
caseobjectCheeseextendsTopping
379
+
caseobjectPepperoniextendsTopping
380
+
caseobjectBlackOlivesextendsTopping
381
+
caseobjectGreenOlivesextendsTopping
382
+
caseobjectOnionsextendsTopping
383
+
384
+
// the implementation of `toppingPrice` above
385
+
defprice(t: Topping):Double= ...
386
+
}
387
+
```
388
+
389
+
{% endtab %}
390
+
{% tab 'Scala 3' for=org_1 %}
313
391
314
392
```scala
315
393
caseclassPizza(
@@ -329,9 +407,7 @@ enum Topping:
329
407
// the companion object of enumeration Topping
330
408
objectTopping:
331
409
// the implementation of `toppingPrice` above
332
-
defprice(t: Topping):Double= t match
333
-
caseCheese|Onions=>0.5
334
-
casePepperoni|BlackOlives|GreenOlives=>0.75
410
+
defprice(t: Topping):Double= ...
335
411
```
336
412
337
413
{% endtab %}
@@ -643,8 +719,8 @@ This can have multiple advantages:
643
719
644
720
Let us revisit our example once more.
645
721
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 %}
648
724
649
725
```scala
650
726
caseclassPizza(
@@ -653,7 +729,7 @@ case class Pizza(
653
729
toppings: Seq[Topping]
654
730
)
655
731
656
-
extension(p: Pizza)
732
+
implicitclassPizzaOps(p: Pizza) {
657
733
defprice:Double=
658
734
pizzaPrice(p) // implementation from above
659
735
@@ -668,15 +744,46 @@ extension (p: Pizza)
668
744
669
745
defupdateCrustType(ct: CrustType):Pizza=
670
746
p.copy(crustType = ct)
747
+
}
671
748
```
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`.
672
752
673
753
{% endtab %}
674
-
{% endtabs %}
754
+
{% tab 'Scala 3' for=module_7 %}
755
+
756
+
```scala
757
+
caseclassPizza(
758
+
crustSize: CrustSize,
759
+
crustType: CrustType,
760
+
toppings: Seq[Topping]
761
+
)
762
+
763
+
extension (p: Pizza)
764
+
defprice:Double=
765
+
pizzaPrice(p) // implementation from above
766
+
767
+
defaddTopping(t: Topping):Pizza=
768
+
p.copy(toppings = p.toppings :+ t)
769
+
770
+
defremoveAllToppings:Pizza=
771
+
p.copy(toppings =Seq.empty)
675
772
773
+
defupdateCrustSize(cs: CrustSize):Pizza=
774
+
p.copy(crustSize = cs)
775
+
776
+
defupdateCrustType(ct: CrustType):Pizza=
777
+
p.copy(crustType = ct)
778
+
```
676
779
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 %}
678
785
679
-
This way, we can obtain the same API as before
786
+
Using our extension methods, we can obtain the same API as before:
0 commit comments