-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathChapter2.tex
More file actions
340 lines (314 loc) · 10.4 KB
/
Chapter2.tex
File metadata and controls
340 lines (314 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
\newpage
%----------------------------------------------------------------------------------------------------
\section{Dealing with Cards}
%----------------------------------------------------------------------------------------------------
\vspace{10cm}
\hrule
\lhQ What is something simple we could begin to solve?
\lhA Comparing cards.
\lhN How do we proceed?
\lhA Write a failing test.
\lhN Ok. Let's compare a \clubs 6 and a \spades 6. These two cards should be considered equals in value.
\begin{lstlisting}[frame=single]
module Tests
where
import Test.HUnit
main = runTestTT $ TestList
[compare "6♣" "6♠" ~?= EQ]
\end{lstlisting} % $
What is the result?
\lhA \failure The result is failure:
\begin{small}
\begin{alltt}
expected: EQ
but got: LT
\end{alltt}
\end{small}
But it's not a big matter, since we're comparing \il!String!s when we should compare \il!Card!s.
\lhN What is a \il!Card!?
\lhA It's a new data type.
\lhN How do I create values of this type?
\lhA Pretend you have a function from \il!String! to \il!Card!.
\lhN Ok. I'll just call that function \il!card! :
\begin{lstlisting}[frame=single]
main = runTestTT $ TestList
[compare (card "6♣") (card "6♠") ~?= EQ]
\end{lstlisting} % $
What now?
\lhA \error Error, as expected. Let me just write the function.
\begin{lstlisting}[frame=single]
module PokerHand
where
card :: String -> Card
\end{lstlisting}
\lhN \error This results in two errors:
\begin{small}
\begin{alltt}
The type signature for `card' lacks an
accompanying binding
Not in scope: type constructor or class `Card'
\end{alltt}
\end{small}
Can you write provide the missing parts?
\lhA \error OK, this is the \il!Card! type:
\begin{lstlisting}[frame=single]
data Card = C
\end{lstlisting}
It has just a single value, \il!C!. And we implement the function
\begin{lstlisting}[frame=single]
card :: String -> Card
card _ = C
\end{lstlisting}
which is just producing the single value.
\lhN \error Now we have another error:
\begin{small}
\begin{alltt}
No instance for (Ord Card) arising from a use
of `compare'
Possible fix: add an instance declaration
for (Ord Card)
\end{alltt}
\end{small}
Should we make the suggested fix?
\lhA \error Sure:
\begin{lstlisting}[frame=single]
data Card = C deriving (Ord)
card :: String -> Card
card _ = C
\end{lstlisting}
\lhN \error Now we have this:
\begin{small}
\begin{alltt}
No instance for (Eq Card) arising from a use
of `compare'
Possible fix: add an instance declaration
for (Eq Card)
\end{alltt}
\end{small}
\lhA \error Again, let's do what the compiler suggests
\begin{lstlisting}[frame=single]
data Card = C deriving (Ord,Eq)
card :: String -> Card
card _ = C
\end{lstlisting}
\success And the test passes.
\lhN Of course, this is just a \emph{fake} implementation of the function \il!card!.
\lhA Then write another test.
\lhN Here you go:
\begin{lstlisting}[frame=single]
main = runTestTT $ TestList
[compare (card "6♣") (card "6♠") ~?= EQ
,compare (card "6♣") (card "5♠") ~?= GT]
\end{lstlisting} % $
How do we make it pass?
\lhA \error We have to compare the rank values of the cards, so we should store this value in the \il!Card! type:
\begin{lstlisting}[frame=single]
data Card = C Value deriving (Ord,Eq)
type Value = Int
card :: String -> Card
card _ = C 0
\end{lstlisting}
\failure Of course, the test now fails, as we must calculate the real value instead of returning zero. Let's think..
\lhN \failure Just make the test pass. I don't like having to think on a red bar.
\lhA \failure Let's play ``\emph{fake it 'til you make it}'' then:
\begin{lstlisting}[frame=single]
card :: String -> Card
card ['6',_] = C 6
card ['5',_] = C 5
\end{lstlisting}
\success Now it's obvious.
\lhN \success Indeed, just convert from \il!Char! to \il!Int!, using the \il!ord! function. Do it.
\lhA \success Ok!
\begin{lstlisting}[frame=single]
module PokerHand
where
import Char
data Card = C Value deriving (Ord,Eq)
type Value = Int
card :: String -> Card
card [c,_] = C $ (ord c) - (ord '0')
\end{lstlisting} % $
\success Done.
\lhN Done? I think I have a new test to write. But first I'll do some refactoring, too.
\begin{lstlisting}[frame=single]
main = runTestTT $ TestList
[compare (card "6♣") (card "6♠") ~?= EQ
,compare (card "6♣") (card "5♠") ~?= GT]
\end{lstlisting} % $
You know about \il!comparing! right?
\lhA Yes, and so does $GHCI$: \\
\il!comparing :: (Ord a) => (b -> a) -> b -> b -> Ordering! \\
\il! -- Defined in Data.Ord! \\
\il!comparing! takes a function from a type \il!b! to an ordered type \il!a!, two values of type \il!b! and gives the comparison using the given function.
\lhN Yes, so I can compare \il!String!s using the \il!card! function:
\begin{lstlisting}[frame=single]
import Data.Ord (comparing)
main = runTestTT $ TestList
[comparing card "6♣" "6♠" ~?= EQ
,comparing card "6♣" "5♠" ~?= GT]
\end{lstlisting} % $
\lhA \success Nice!
\lhN Now for my new test:
\begin{lstlisting}[frame=single]
main = runTestTT $ TestList
[comparing card "6♣" "6♠" ~?= EQ
,comparing card "6♣" "5♠" ~?= GT
,comparing card "T♣" "J♠" ~?= LT]
\end{lstlisting} % $
\failure We're expecting \il!LT! but get \il!GT!. Can you make it pass?
\lhA
\failure Sure:
\begin{lstlisting}[frame=single]
card :: String -> Card
card ['J',_] = C 11
card ['T',_] = C 10
card [c,_] = C $ (ord c) - (ord '0')
\end{lstlisting} % $
\success We just have to add special cases.
\lhN Good. Here's a new one:
\begin{lstlisting}[frame=single]
main = runTestTT $ TestList
[comparing card "6♣" "6♠" ~?= EQ
,comparing card "6♣" "5♠" ~?= GT
,comparing card "T♣" "J♠" ~?= LT
,comparing card "K♣" "A♣" ~?= LT]
\end{lstlisting} % $
\lhA \failure Ok.
\begin{lstlisting}[frame=single]
card :: String -> Card
card ['A',_] = C 14
card ['K',_] = C 13
card ['J',_] = C 11
card ['T',_] = C 10
card [c,_] = C $ (ord c) - (ord '0')
\end{lstlisting} % $
\success That's easy: give each card its value.
\lhN We forgot the Queen value:
\begin{lstlisting}[frame=single]
main = runTestTT $ TestList
[comparing card "6♣" "6♠" ~?= EQ
,comparing card "6♣" "5♠" ~?= GT
,comparing card "T♣" "J♠" ~?= LT
,comparing card "K♣" "A♣" ~?= LT
,comparing card "Q♣" "K♣" ~?= LT]
\end{lstlisting} % $
Can you add it?
\lhA \failure Sure:
\begin{lstlisting}[frame=single]
card :: String -> Card
card ['A',_] = C 14
card ['K',_] = C 13
card ['Q',_] = C 12
card ['J',_] = C 11
card ['T',_] = C 10
card [c,_] = C $ (ord c) - (ord '0')
\end{lstlisting} % $
\success And we are done with card values.
\lhN We are, but these tests are a bit heavy. Can you think of a way to avoid repeating all these comparisons?
\lhA Yes: we could test the sorting of a deck.
\lhN
\begin{lstlisting}[frame=single]
module Tests
where
import Test.HUnit
import PokerHand
import Data.List (sort)
ud = map card ["A♣","2♣","T♣","K♣","9♣","Q♣","J♣"]
sd = map card ["2♣","9♣","T♣","J♣","Q♣","K♣","A♣"]
main = runTestTT $ TestList
[sort ud ~?= sd]
\end{lstlisting} % $
Is that what you mean?
\lhA \error Yes, but we have a new problem.
\lhN \error Indeed:
\begin{small}
\begin{alltt}
No instance for (Show Card) arising from a use
of `~?='
Possible fix: add an instance declaration
for (Show Card)
\end{alltt}
\end{small}
Should we follow the suggestion?
\lhA \error No. I don't think the \il!Card! type should derive the \il!Show! class just for testing reasons.
\lhN Then should we get back to the previous version of our tests?
\lhA I have a better idea: instead of comparing lists of \il!Card!s we can compare lists of \il!String!s.
\lhN \error Comparing the \il!String!s? Ok:
\begin{lstlisting}[frame=single]
ud = ["A♣","2♣","T♣","K♣","9♣","Q♣","J♣"]
sd = ["2♣","9♣","T♣","J♣","Q♣","K♣","A♣"]
main = runTestTT $ TestList
[sort ud ~?= sd]
\end{lstlisting} % $
\failure But now the test fails:
\begin{small}
\begin{alltt}
expected: ["2\monoclubs","9\monoclubs","T\monoclubs","J\monoclubs","Q\monoclubs","K\monoclubs","A\monoclubs"]
but got: ["2\monoclubs","9\monoclubs","A\monoclubs","J\monoclubs","K\monoclubs","Q\monoclubs","T\monoclubs"]
\end{alltt}
\end{small}
Do you see why?
\lhA \failure Of course: we don't use \il!Card!s any more! We should compare the \il!String!s using the \il!card! function. The function \\
\il!sortBy :: (a -> a -> Ordering) -> [a] -> [a]! \\
allows us to do that.
\lhN You mean like this:
\begin{lstlisting}[frame=single]
import Data.Ord (comparing)
import Data.List (sortBy)
ud = ["A♣","2♣","T♣","K♣","9♣","Q♣","J♣"]
sd = ["2♣","9♣","T♣","J♣","Q♣","K♣","A♣"]
main = runTestTT $ TestList
[sortBy (comparing card) ud ~?= sd]
\end{lstlisting} % $
\lhA \success Yes!
\newpage \lhN I wonder what would the test show if it failed. Let's falsify it:
\begin{lstlisting}[frame=single]
import Data.Ord (comparing)
import Data.List (sortBy)
ud = ["3♣","2♣","T♣","K♣","9♣","Q♣","J♣"]
sd = ["2♣","9♣","T♣","J♣","Q♣","K♣","A♣"]
main = runTestTT $ TestList
[sortBy (comparing card) ud ~?= sd]
\end{lstlisting} % $
I just changed the first value of the unsorted desk.
\lhA \failure Here is what the message says:
\begin{small}
\begin{alltt}
expected: ["2\monoclubs","9\monoclubs","T\monoclubs","J\monoclubs","Q\monoclubs","K\monoclubs","A\monoclubs"]
but got: ["2\monoclubs","3\monoclubs","9\monoclubs","T\monoclubs","J\monoclubs","Q\monoclubs","K\monoclubs"]
\end{alltt}
\end{small}
The test properly outputs the results as a list of \il!String!s. You can un-falsify the test now.
\lhN Yes.
\lhA Oh, and using \il!words! for the definition of our decks would make the code prettier.
\lhN You are right. So this is the test code:
\begin{lstlisting}[frame=single]
module Tests
where
import Test.HUnit
import PokerHand
import Data.Ord (comparing)
import Data.List (sortBy)
ud = words "A♣ 2♣ T♣ K♣ 9♣ Q♣ J♣"
sd = words "2♣ 9♣ T♣ J♣ Q♣ K♣ A♣"
main = runTestTT $ TestList
[sortBy (comparing card) ud ~?= sd]
\end{lstlisting} % $
\lhA \success And this is the tested code:
\begin{lstlisting}[frame=single]
module PokerHand
where
import Char
data Card = C Value deriving (Ord,Eq)
type Value = Int
card :: String -> Card
card ['A',_] = C 14
card ['K',_] = C 13
card ['Q',_] = C 12
card ['J',_] = C 11
card ['T',_] = C 10
card [c,_] = C $ (ord c) - (ord '0')
\end{lstlisting} %$
\lhN Are we done with comparing \il!Cards!?
\lhA Not yet, but it's time for a break.
\lhend