Skip to content

Commit ccd597b

Browse files
authored
Ch6 - Add deriving exercises (#240)
* Ch6 - Add deriving exercises * Refine Semiring Complex solution with Newtype
1 parent 5be5ba7 commit ccd597b

File tree

3 files changed

+243
-49
lines changed

3 files changed

+243
-49
lines changed

exercises/chapter6/test/Main.purs

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,72 @@ main =
1919
runChapterExamples
2020
{- Move this block comment starting point to enable more tests
2121
Note to reader: Delete this line to expand comment block -}
22-
test "Exercise Group - Show Me" do
23-
-- Tests for the first exercise in this chapter (Show Shape)
24-
-- can be found at the end of the previous chapter (chapter 5).
25-
Assert.equal true true
22+
suite "Exercise Group - Show Me" do
23+
test "Exercise - Show Point" do
24+
Assert.equal "(1.0, 2.0)"
25+
$ show
26+
$ Point {x: 1.0, y: 2.0}
2627
suite "Exercise Group - Common Type Classes" do
27-
suite "Exercise - Show and Eq for Complex" do
28-
test "Show Complex" do
28+
let cpx real imaginary = Complex {real, imaginary}
29+
suite "Exercise - Show Complex" do
30+
test "Show" do
2931
Assert.equal "1.0+2.0i"
3032
$ show
31-
$ Complex { real: 1.0, imaginary: 2.0 }
32-
test "Show Negative Complex" do
33+
$ cpx 1.0 2.0
34+
test "Show Negative" do
3335
Assert.equal "1.0-2.0i"
3436
$ show
35-
$ Complex { real: 1.0, imaginary: -2.0 }
36-
test "Eq Complex" do
37-
Assert.equal (Complex { real: 1.0, imaginary: 2.0 })
38-
$ Complex { real: 1.0, imaginary: 2.0 }
39-
test "Eq Complex - not equal" do
37+
$ cpx 1.0 (-2.0)
38+
suite "Exercise - Eq Complex" do
39+
test "equal" do
40+
Assert.equal (cpx 1.0 2.0)
41+
$ cpx 1.0 2.0
42+
test "not equal" do
4043
Assert.expectFailure "should not be equal"
41-
$ Assert.equal (Complex { real: 5.0, imaginary: 2.0 })
42-
$ Complex { real: 1.0, imaginary: 2.0 }
44+
$ Assert.equal (cpx 5.0 2.0)
45+
$ cpx 1.0 2.0
46+
suite "Exercise - Semiring Complex" do
47+
test "add" do
48+
Assert.equal (cpx 4.0 6.0)
49+
$ add (cpx 1.0 2.0) (cpx 3.0 4.0)
50+
test "multiply" do
51+
Assert.equal (cpx (-5.0) 10.0)
52+
$ mul (cpx 1.0 2.0) (cpx 3.0 4.0)
53+
suite "Exercise - Ring Complex" do
54+
test "subtract" do
55+
Assert.equal (cpx 2.0 3.0)
56+
$ sub (cpx 3.0 5.0) (cpx 1.0 2.0)
57+
suite "Exercise - Show Shape" do
58+
test "circle" do
59+
Assert.equal "(Circle (1.0, 2.0) 3.0)"
60+
$ show $ Circle (Point {x: 1.0, y: 2.0}) 3.0
61+
test "rectangle" do
62+
Assert.equal "(Rectangle (1.0, 2.0) 3.0 4.0)"
63+
$ show $ Rectangle (Point {x: 1.0, y: 2.0}) 3.0 4.0
64+
test "line" do
65+
Assert.equal "(Line (1.0, 2.0) (3.0, 4.0))"
66+
$ show $ Line (Point {x: 1.0, y: 2.0}) (Point {x: 3.0, y: 4.0})
67+
test "text" do
68+
Assert.equal "(Text (1.0, 2.0) \"Hello\")"
69+
$ show $ Text (Point {x: 1.0, y: 2.0}) "Hello"
70+
let
71+
withDups =
72+
[ Circle (Point {x: 1.0, y: 2.0}) 3.0
73+
, Circle (Point {x: 3.0, y: 2.0}) 3.0
74+
, Circle (Point {x: 1.0, y: 2.0}) 3.0
75+
, Circle (Point {x: 2.0, y: 2.0}) 3.0
76+
]
77+
noDups =
78+
[ Circle (Point {x: 1.0, y: 2.0}) 3.0
79+
, Circle (Point {x: 3.0, y: 2.0}) 3.0
80+
, Circle (Point {x: 2.0, y: 2.0}) 3.0
81+
]
82+
test "Exercise - dedupShapes" do
83+
Assert.equal noDups
84+
$ dedupShapes withDups
85+
test "Exercise - dedupShapesFast" do
86+
Assert.equal noDups
87+
$ dedupShapesFast withDups
4388
suite "Exercise Group - Constraints and Dependencies" do
4489
suite "Exercise - Eq for NonEmpty" do
4590
test "NonEmpty equals" do

exercises/chapter6/test/no-peeking/Solutions.purs

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
module Test.NoPeeking.Solutions where
22

33
import Prelude
4-
import Data.Array (length, nubByEq)
4+
5+
import Data.Array (length, nub, nubByEq, nubEq)
56
import Data.Foldable (class Foldable, foldMap, foldl, foldr, maximum)
7+
import Data.Generic.Rep (class Generic)
8+
import Data.Generic.Rep.Show (genericShow)
69
import Data.Hashable (class Hashable, hash, hashEqual)
710
import Data.Maybe (Maybe(..))
811
import Data.Monoid (power)
12+
import Data.Newtype (class Newtype, over2, wrap)
13+
14+
data Point
15+
= Point
16+
{ x :: Number
17+
, y :: Number
18+
}
19+
20+
instance showPoint :: Show Point where
21+
show (Point p) =
22+
"(" <> show p.x <> ", " <> show p.y <> ")"
923

1024
newtype Complex
1125
= Complex
@@ -22,38 +36,120 @@ instance showComplex :: Show Complex where
2236
in
2337
show c.real <> optional_plus <> show c.imaginary <> "i"
2438

39+
derive instance eqComplex :: Eq Complex
40+
{-
41+
-- Manual solution
2542
instance eqComplex :: Eq Complex where
26-
eq (Complex a) (Complex b) = a.real == b.real && a.imaginary == b.imaginary
43+
eq (Complex a) (Complex b) = a == b
44+
-- or
45+
-- eq (Complex a) (Complex b) = a.real == b.real && a.imaginary == b.imaginary
46+
-}
47+
48+
derive instance newtypeComplex :: Newtype Complex _
49+
50+
instance semiringComplex :: Semiring Complex where
51+
add = over2 Complex add
52+
mul = over2 Complex
53+
\ { real: r1, imaginary: i1 }
54+
{ real: r2, imaginary: i2 }
55+
->
56+
{ real: r1 * r2 - i1 * i2
57+
, imaginary: r1 * i2 + r2 * i1
58+
}
59+
zero = wrap zero
60+
one = wrap one
61+
{-
62+
-- Without Newtype
63+
instance semiringComplex :: Semiring Complex where
64+
add (Complex c1) (Complex c2) = Complex $ c1 + c2
65+
mul
66+
(Complex { real: r1, imaginary: i1 })
67+
(Complex { real: r2, imaginary: i2 })
68+
= Complex
69+
{ real: r1 * r2 - i1 * i2
70+
, imaginary: r1 * i2 + r2 * i1
71+
}
72+
zero = Complex zero
73+
one = Complex one
74+
-- Could instead write `zero` and `one` more explicitly
75+
--zero = Complex {real: 0.0, imaginary: 0.0}
76+
--one = Complex {real: 1.0, imaginary: 1.0}
77+
-}
78+
79+
derive newtype instance ringComplex :: Ring Complex
80+
{-
81+
-- Manual solution
82+
instance ringComplex :: Ring Complex where
83+
sub (Complex a) (Complex b) = Complex $ a - b
84+
-}
85+
86+
data Shape
87+
= Circle Point Number
88+
| Rectangle Point Number Number
89+
| Line Point Point
90+
| Text Point String
91+
92+
derive instance genericShape :: Generic Shape _
93+
94+
instance showShape :: Show Shape where
95+
show = genericShow
96+
{-
97+
-- Manual solution
98+
instance showShape :: Show Shape where
99+
show (Circle p r) = "(Circle " <> show p <> " " <> show r <> ")"
100+
show (Rectangle p l w) = "(Rectangle " <> show p <> " " <> show l <> " " <> show w <> ")"
101+
show (Line p1 p2) = "(Line " <> show p1 <> " " <> show p2 <> ")"
102+
show (Text p s) = "(Text " <> show p <> " " <> show s <> ")"
103+
-}
27104

28105
data NonEmpty a
29106
= NonEmpty a (Array a)
30107

31108
instance eqNonEmpty :: Eq a => Eq (NonEmpty a) where
32109
eq (NonEmpty e1 a1) (NonEmpty e2 a2) = e1 == e2 && a1 == a2
110+
{-
111+
-- Derived solution
112+
derive instance eqNonEmpty :: Eq a => Eq (NonEmpty a)
113+
-}
33114

34115
instance semigroupNonEmpty :: Semigroup (NonEmpty a) where
35116
append (NonEmpty e1 a1) (NonEmpty e2 a2) = NonEmpty e1 (a1 <> [ e2 ] <> a2)
36117

37118
instance showNonEmpty :: Show a => Show (NonEmpty a) where
38119
show (NonEmpty e1 a1) = show e1 <> " " <> show a1
39120

121+
derive instance functorNonEmpty :: Functor NonEmpty
122+
{-
123+
-- Manual solution
40124
instance functorNonEmpty :: Functor NonEmpty where
41125
map func (NonEmpty e1 a1) = NonEmpty (func e1) (map func a1)
126+
-}
42127

43128
data Extended a
44-
= Finite a
45-
| Infinite
129+
= Infinite
130+
| Finite a
46131

132+
derive instance eqExtended :: Eq a => Eq (Extended a)
133+
{-
134+
-- Manual Eq
47135
instance eqExtended :: Eq a => Eq (Extended a) where
48136
eq Infinite Infinite = true
49137
eq (Finite e1) (Finite e2) = e1 == e2
50138
eq _ _ = false
139+
-}
51140

52141
instance ordExtended :: Ord a => Ord (Extended a) where
53142
compare Infinite Infinite = EQ
54143
compare Infinite (Finite _) = GT
55144
compare (Finite _) Infinite = LT
56145
compare (Finite v1) (Finite v2) = compare v1 v2
146+
{-
147+
-- Note that it would have been possible to derive Ord if
148+
-- the constructor order was reversed, although using implicit
149+
-- ordering may make our intentions less clear if we care about
150+
-- how things are ordered.
151+
derive instance ordExtended :: Ord a => Ord (Extended a)
152+
-}
57153

58154
instance foldableNonEmpty :: Foldable NonEmpty where
59155
foldr func st (NonEmpty val arr) = foldr func st ([ val ] <> arr)
@@ -72,6 +168,18 @@ instance foldableOneMore :: Foldable f => Foldable (OneMore f) where
72168
firstB = (func st val)
73169
foldMap func (OneMore val more) = (func val) <> (foldMap func more)
74170

171+
derive instance eqPoint :: Eq Point
172+
derive instance eqShape :: Eq Shape
173+
174+
dedupShapes :: Array Shape -> Array Shape
175+
dedupShapes = nubEq
176+
177+
derive instance ordPoint :: Ord Point
178+
derive instance ordShape :: Ord Shape
179+
180+
dedupShapesFast :: Array Shape -> Array Shape
181+
dedupShapesFast = nub
182+
75183
unsafeMaximum :: Partial => Array Int -> Int
76184
unsafeMaximum arr = case maximum arr of
77185
Just m -> m

text/chapter6.md

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,15 @@ No type class instance was found for
125125

126126
## Exercises
127127

128-
1. (Easy) Use the `showShape` function from the previous chapter to define a `Show` instance for the `Shape` type.
128+
1. (Easy) Define a `Show` instance for `Point`. Match the same output as the `showPoint` function from the previous chapter.
129+
130+
```haskell
131+
data Point
132+
= Point
133+
{ x :: Number
134+
, y :: Number
135+
}
136+
```
129137

130138
## Common Type Classes
131139

@@ -304,18 +312,41 @@ Whatever "lifting" means in the general sense, it should be true that any reason
304312

305313
Many standard type classes come with their own set of similar laws. The laws given to a type class give structure to the functions of that type class and allow us to study its instances in generality. The interested reader can research the laws ascribed to the standard type classes that we have seen already.
306314

307-
## Exercises
315+
### Deriving Instances
308316

309-
1. (Easy) The following newtype represents a complex number:
317+
Rather than writing instances manually, you can let the compiler to most of the work for you. Take a look at this [Type Class Deriving guide](https://github.com/purescript/documentation/blob/master/guides/Type-Class-Deriving.md). That information will help you solve the following exercises.
310318

311-
```haskell
312-
newtype Complex = Complex
313-
{ real :: Number
314-
, imaginary :: Number
315-
}
316-
```
319+
## Exercises
320+
321+
The following newtype represents a complex number:
322+
323+
```haskell
324+
newtype Complex
325+
= Complex
326+
{ real :: Number
327+
, imaginary :: Number
328+
}
329+
```
330+
331+
1. (Easy) Define a `Show` instance for `Complex`. Match the output format expected by the tests (e.g. `1.2+3.4i`, `5.6-7.8i`, etc.).
332+
333+
2. (Easy) Derive an `Eq` instance for `Complex`. _Note_: You may instead write this instance manually, but why do more work if you don't have to?
334+
335+
3. (Medium) Define a `Semiring` instance for `Complex`. _Note_: You can use `wrap` and `over2` from [`Data.Newtype`](https://pursuit.purescript.org/packages/purescript-newtype/docs/Data.Newtype) to create a more concise solution.
336+
337+
4. (Easy) Derive (via `newtype`) a `Ring` instance for `Complex`. _Note_: You may instead write this instance manually, but that's not as convenient.
338+
339+
Here's the `Shape` ADT from the previous chapter:
340+
341+
```haskell
342+
data Shape
343+
= Circle Point Number
344+
| Rectangle Point Number Number
345+
| Line Point Point
346+
| Text Point String
347+
```
317348

318-
Define `Show` and `Eq` instances for `Complex`.
349+
5. (Medium) Derive (via `Generic`) a `Show` instance for `Shape`. How does the amount of code written and `String` output compare to `showShape` from the previous chapter? _Note_: You may instead write this instance manually, but you'll need to pay close attention to the output format expected by the tests.
319350

320351
## Type Class Constraints
321352

@@ -454,35 +485,45 @@ When the program is compiled, the correct type class instance for `Show` is chos
454485

455486

456487
## Exercises
457-
1. (Easy) The following declaration defines a type of non-empty arrays of elements of type `a`:
488+
489+
1. (Easy) The following declaration defines a type of non-empty arrays of elements of type `a`:
490+
491+
```haskell
492+
data NonEmpty a = NonEmpty a (Array a)
493+
```
494+
495+
Write an `Eq` instance for the type `NonEmpty a` which reuses the instances for `Eq a` and `Eq (Array a)`. _Note:_ you may instead derive the `Eq` instance.
496+
497+
1. (Medium) Write a `Semigroup` instance for `NonEmpty a` by reusing the `Semigroup` instance for `Array`.
498+
499+
1. (Medium) Write a `Functor` instance for `NonEmpty`.
500+
501+
1. (Medium) Given any type `a` with an instance of `Ord`, we can add a new "infinite" value which is greater than any other value:
458502

459503
```haskell
460-
data NonEmpty a = NonEmpty a (Array a)
504+
data Extended a = Infinite | Finite a
461505
```
462506

463-
Write an `Eq` instance for the type `NonEmpty a` which reuses the instances for `Eq a` and `Eq (Array a)`.
464-
1. (Medium) Write a `Semigroup` instance for `NonEmpty a` by reusing the `Semigroup` instance for `Array`.
465-
1. (Medium) Write a `Functor` instance for `NonEmpty`.
466-
1. (Medium) Given any type `a` with an instance of `Ord`, we can add a new "infinite" value which is greater than any other value:
507+
Write an `Ord` instance for `Extended a` which reuses the `Ord` instance for `a`.
467508

468-
```haskell
469-
data Extended a = Finite a | Infinite
470-
```
509+
1. (Difficult) Write a `Foldable` instance for `NonEmpty`. _Hint_: reuse the `Foldable` instance for arrays.
471510

472-
Write an `Ord` instance for `Extended a` which reuses the `Ord` instance for `a`.
473-
1. (Difficult) Write a `Foldable` instance for `NonEmpty`. _Hint_: reuse the `Foldable` instance for arrays.
474-
1. (Difficult) Given a type constructor `f` which defines an ordered container (and so has a `Foldable` instance), we can create a new container type which includes an extra element at the front:
511+
1. (Difficult) Given a type constructor `f` which defines an ordered container (and so has a `Foldable` instance), we can create a new container type which includes an extra element at the front:
475512

476-
```haskell
477-
data OneMore f a = OneMore a (f a)
478-
```
513+
```haskell
514+
data OneMore f a = OneMore a (f a)
515+
```
479516

480-
The container `OneMore f` also has an ordering, where the new element comes before any element of `f`. Write a `Foldable` instance for `OneMore f`:
517+
The container `OneMore f` also has an ordering, where the new element comes before any element of `f`. Write a `Foldable` instance for `OneMore f`:
481518

482-
```haskell
483-
instance foldableOneMore :: Foldable f => Foldable (OneMore f) where
484-
...
485-
```
519+
```haskell
520+
instance foldableOneMore :: Foldable f => Foldable (OneMore f) where
521+
...
522+
```
523+
524+
1. (Medium) Write a `dedupShapes :: Array Shape -> Array Shape` function which removes duplicate `Shape`s from an array using the `nubEq` function.
525+
526+
1. (Medium) Write a `dedupShapesFast` function which is the same as `dudupShapes`, but uses the more efficient `nub` function.
486527

487528
## Multi Parameter Type Classes
488529

0 commit comments

Comments
 (0)