@@ -3,4 +3,224 @@ package scalatutorial.sections
33/** @param name structuring_information */
44object StructuringInformation extends ScalaTutorialSection {
55
6+ /**
7+ * = Introduction =
8+ *
9+ * Programs are systems that process information. Therefore, programming
10+ * languages provide ways to model the domain of a program.
11+ *
12+ * This section introduces the ways you can structure information in Scala.
13+ * We will base our examples on the following domain, a ''music sheet'':
14+ *
15+ * <img src="/assets/scala_tutorial/music_sheet.png" />
16+ *
17+ * = Aggregating Information With Case Classes =
18+ *
19+ * First, let’s focus on ''notes''. Suppose that, in our program, we are
20+ * interested in the following properties of notes: their
21+ * [[https://en.wikipedia.org/wiki/Musical_note#12-tone_chromatic_scale name ]]
22+ * (A, B, C, etc.), their
23+ * [[https://en.wikipedia.org/wiki/Note_value duration ]] (whole, half,
24+ * quarter, etc.) and their octave number.
25+ *
26+ * In summary, our note model ''aggregates'' several data (name,
27+ * duration and octave). We express this in Scala by using a ''case class''
28+ * definition:
29+ *
30+ * {{{
31+ * case class Note(
32+ * name: String,
33+ * duration: String,
34+ * octave: Int
35+ * )
36+ * }}}
37+ *
38+ * This definition introduces a new type, `Note`. You can create values
39+ * of this type by calling its ''constructor'':
40+ *
41+ * {{{
42+ * val c3 = Note("C", "Quarter", 3)
43+ * }}}
44+ *
45+ * Then, you can retrieve the information carried by each ''member'' (`name`,
46+ * `duration` and `octave`) by using the dot notation:
47+ */
48+ def caseClassProjection (res0 : String , res1 : Int ): Unit = {
49+ case class Note (name : String , duration : String , octave : Int )
50+ val c3 = Note (" C" , " Quarter" , 3 )
51+ c3.name shouldBe " C"
52+ c3.duration shouldBe res0
53+ c3.octave shouldBe res1
54+ }
55+
56+ /**
57+ * = Defining Alternatives With Sealed Traits =
58+ *
59+ * If we look at the introductory picture, we see that musical symbols
60+ * can be either ''notes'' or ''rests'' (but nothing else).
61+ *
62+ * So, we want to introduce the concept of ''symbol'', as something
63+ * that can be embodied by a fixed set of alternatives: a note or rest.
64+ * We can express this in Scala using a ''sealed trait'' definition:
65+ *
66+ * {{{
67+ * sealed trait Symbol
68+ * case class Note(name: String, duration: String, octave: Int) extends Symbol
69+ * case class Rest(duration: String) extends Symbol
70+ * }}}
71+ *
72+ * A sealed trait definition introduces a new type (here, `Symbol`), but no
73+ * constructor for it. Constructors are defined by alternatives that
74+ * ''extend'' the sealed trait:
75+ *
76+ * {{{
77+ * val symbol1: Symbol = Note("C", "Quarter", 3)
78+ * val symbol2: Symbol = Rest("Whole")
79+ * }}}
80+ *
81+ * = Pattern Matching =
82+ *
83+ * Since the `Symbol` type has no members, we can not do anything
84+ * useful when we manipulate one. We need a way to distinguish between
85+ * the different cases of symbols. ''Pattern matching'' allows us
86+ * to do so:
87+ *
88+ * {{{
89+ * def symbolDuration(symbol: Symbol): String =
90+ * symbol match {
91+ * case Note(name, duration, octave) => duration
92+ * case Rest(duration) => duration
93+ * }
94+ * }}}
95+ *
96+ * The above `match` expression first checks if the given `Symbol` is a
97+ * `Note`, and if it is the case it extracts its fields (`name`, `duration`
98+ * and `octave`) and evaluates the expression at the right of the arrow.
99+ * Otherwise, it checks if the given `Symbol` is a `Rest`, and if it
100+ * is the case it extracts its `duration` field, and evaluates the
101+ * expression at the right of the arrow.
102+ *
103+ * When we write `case Rest(duration) => …`, we say that `Rest(…)` is a
104+ * ''constructor pattern'': it matches all the values of type `Rest`
105+ * that have been constructed with arguments matching the pattern `duration`.
106+ *
107+ * The pattern `duration` is called a ''variable pattern''. It matches
108+ * any value and ''binds'' its name (here, `duration`) to this value.
109+ *
110+ * More generally, an expression of the form
111+ *
112+ * {{{
113+ * e match {
114+ * case p1 => e1
115+ * case p2 => e2
116+ * …
117+ * case pn => pn
118+ * }
119+ * }}}
120+ *
121+ * matches the value of the selector `e` with the patterns
122+ * `p1`, …, `pn` in the order in which they are written.
123+ *
124+ * The whole match expression is rewritten to the right-hand side of the first
125+ * case where the pattern matches the selector `e`.
126+ *
127+ * References to pattern variables are replaced by the corresponding
128+ * parts in the selector.
129+ *
130+ * == Exhaustivity ==
131+ *
132+ * Having defined `Symbol` as a sealed trait gives us the guarantee that
133+ * the possible case of symbols is fixed. The compiler can leverage this
134+ * knowledge to warn us if we write code that does not handle ''all''
135+ * the cases:
136+ */
137+ def unexhaustive (): Unit = {
138+ sealed trait Symbol
139+ case class Note (name : String , duration : String , octave : Int ) extends Symbol
140+ case class Rest (duration : String ) extends Symbol
141+
142+ def nonExhaustiveDuration (symbol : Symbol ): String =
143+ symbol match {
144+ case Rest (duration) => duration
145+ }
146+ }
147+
148+ /**
149+ * Try to run the above code to see how the compiler informs us that
150+ * we don’t handle all the cases in `nonExhaustiveDuration`.
151+ *
152+ * = Equals =
153+ *
154+ * It is worth noting that, since the purpose of case classes is to
155+ * aggregate values, comparing case class instances compare their values:
156+ *
157+ */
158+ def caseClassEquals (res0 : Boolean , res1 : Boolean ): Unit = {
159+ case class Note (name : String , duration : String , octave : Int )
160+ val c3 = Note (" C" , " Quarter" , 3 )
161+ val otherC3 = Note (" C" , " Quarter" , 3 )
162+ val f3 = Note (" F" , " Quarter" , 3 )
163+ (c3 == otherC3) shouldBe res0
164+ (c3 == f3) shouldBe res1
165+ }
166+
167+ /**
168+ * = Enumerations =
169+ *
170+ * Our above definition of the `Note` type allows users to create instances
171+ * with invalid names and durations:
172+ *
173+ * {{{
174+ * val invalidNote = Note("not a name", "not a duration", 3)
175+ * }}}
176+ *
177+ * It is generally a bad idea to work with a model that makes it possible
178+ * to reach invalid states. In our case, we want to restrict the space
179+ * of the possible note names and durations to a set of fixed alternatives.
180+ *
181+ * In the case of note names, the alternatives are either `A`, `B`, `C`,
182+ * `D`, `E`, `F` or `G`. We can express the fact that note names are
183+ * a fixed set of alternatives by using a sealed trait, but in contrast to
184+ * the previous example alternatives are not case classes because they
185+ * aggregate no information:
186+ *
187+ * {{{
188+ * sealed trait NoteName
189+ * case object A extends NoteName
190+ * case object B extends NoteName
191+ * case object C extends NoteName
192+ * …
193+ * case object G extends NoteName
194+ * }}}
195+ *
196+ * = Algebraic Data Types =
197+ *
198+ * Data types defined with sealed trait and case classes are called
199+ * ''algebraic data types''. An algebraic data type definition can
200+ * be thought of as a set of possible values.
201+ *
202+ * Algebraic data types are a powerful way to structure information.
203+ *
204+ * If a concept of your program’s domain can be formulated in terms of
205+ * an ''is'' relationship, you will express it with a sealed trait:
206+ *
207+ * “A symbol ''is'' either a note ''or'' a rest.”
208+ *
209+ * {{{
210+ * sealed trait Symbol
211+ * case class Note(…) extends Symbol
212+ * case class Rest(…) extends Symbol
213+ * }}}
214+ *
215+ * On the other hand, if a concept of your program’s domain can be
216+ * formulated in terms of an ''has'' relationship, you will express it
217+ * with a case class:
218+ *
219+ * “A note ''has'' a name, a duration ''and'' an octave number.”
220+ *
221+ * {{{
222+ * case class Note(name: String, duration: String, octave: Int) extends Symbol
223+ * }}}
224+ */
225+ def nothing (): Unit = ()
6226}
0 commit comments