Skip to content

Commit 685e604

Browse files
author
Abhijit Sarkar
committed
Complete ch13
1 parent 0bb7837 commit 685e604

13 files changed

+257
-9
lines changed

.mill-version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.12.4
1+
0.12.5

.scalafmt.conf

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
version = "3.8.4-RC4"
22
align.preset = more
3-
maxColumn = 120
3+
maxColumn = 100
44
runner.dialect = scala3
55
assumeStandardLibraryStripMargin = true

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ The older code is available in branches.
2020
10. [Monad Transformers](ch10)
2121
11. [Semigroupal and Applicative](ch11)
2222
12. [Foldable and Traverse](ch12)
23+
13. [Indexed Types](ch13)
2324

2425
## Running tests
2526
```

ch05/src/ExpressionT.scala

+6-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ enum ExpressionT:
1717
case Loop(expr: ExpressionT, k: Continuation)
1818
case Done(result: Double)
1919

20-
def loop2(left: ExpressionT, right: ExpressionT, cont: Continuation, op: (Double, Double) => Double): Call =
20+
def loop2(
21+
left: ExpressionT,
22+
right: ExpressionT,
23+
cont: Continuation,
24+
op: (Double, Double) => Double
25+
): Call =
2126
Call.Loop(
2227
left,
2328
l => Call.Loop(right, r => Call.Continue(op(l, r), cont))

ch05/test/src/ExpressionCSpec.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ import org.scalatest.matchers.should.Matchers.shouldBe
66
class ExpressionCSpec extends AnyFunSpec:
77
describe("ExpressionC"):
88
it("eval"):
9-
val fortyTwo = ((ExpressionC(15.0) + ExpressionC(5.0)) * ExpressionC(2.0) + ExpressionC(2.0)) / ExpressionC(1.0)
9+
val fortyTwo = ((ExpressionC(15.0) + ExpressionC(5.0)) * ExpressionC(2.0) + ExpressionC(
10+
2.0
11+
)) / ExpressionC(1.0)
1012
fortyTwo.eval shouldBe 42.0d

ch05/test/src/ExpressionSpec.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ import org.scalatest.matchers.should.Matchers.shouldBe
66
class ExpressionSpec extends AnyFunSpec:
77
describe("Expression"):
88
it("eval"):
9-
val fortyTwo = ((Expression(15.0) + Expression(5.0)) * Expression(2.0) + Expression(2.0)) / Expression(1.0)
9+
val fortyTwo =
10+
((Expression(15.0) + Expression(5.0)) * Expression(2.0) + Expression(2.0)) / Expression(1.0)
1011
fortyTwo.eval shouldBe 42.0d

ch05/test/src/ExpressionTSpec.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ import org.scalatest.matchers.should.Matchers.shouldBe
66
class ExpressionTSpec extends AnyFunSpec:
77
describe("ExpressionT"):
88
it("eval"):
9-
val fortyTwo = ((ExpressionT(15.0) + ExpressionT(5.0)) * ExpressionT(2.0) + ExpressionT(2.0)) / ExpressionT(1.0)
9+
val fortyTwo = ((ExpressionT(15.0) + ExpressionT(5.0)) * ExpressionT(2.0) + ExpressionT(
10+
2.0
11+
)) / ExpressionT(1.0)
1012
fortyTwo.eval shouldBe 42.0d

ch06/test/src/CatSpec.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import cats.syntax.eq.catsSyntaxEq
88
class CatSpec extends AnyFunSpec:
99
describe("Cat"):
1010
it("Show"):
11-
Cat("Garfield", 41, "ginger and black").show shouldBe "Garfield is a 41 year-old ginger and black cat."
11+
Cat(
12+
"Garfield",
13+
41,
14+
"ginger and black"
15+
).show shouldBe "Garfield is a 41 year-old ginger and black cat."
1216

1317
it("Eq"):
1418
val cat1 = Cat("Garfield", 38, "orange and black")

ch08/src/ch08.worksheet.sc

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ val showString = Show[String]
3838
// trait Contravariant[F[_]]:
3939
// def contramap[A, B](fa: F[A])(f: B => A): F[B]
4040

41-
val showSymbol: Show[Symbol] = Contravariant[Show].contramap(showString)((sym: Symbol) => s"'${sym.name}")
41+
val showSymbol: Show[Symbol] =
42+
Contravariant[Show].contramap(showString)((sym: Symbol) => s"'${sym.name}")
4243

4344
showSymbol.show(Symbol("dave"))
4445

ch09/src/Lib.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ object Lib:
1616

1717
// def validateAdult[F[_] : ([G[_]] =>> MonadError[G, Throwable]) as me](age: Int): F[Int] =
1818
def validateAdult[F[_]](age: Int)(using me: MonadError[F, Throwable]): F[Int] =
19-
me.ensure(me.pure(age))(IllegalArgumentException("Age must be greater than or equal to 18"))(_ >= 18)
19+
me.ensure(me.pure(age))(IllegalArgumentException("Age must be greater than or equal to 18"))(
20+
_ >= 18
21+
)
2022

2123
/*
2224
9.6.5 Exercise: Safer Folding using Eval

ch13/src/Html.scala

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package ch13
2+
3+
// 13.2.1 API Protocols
4+
// Indexed codata allows us to enforce API protocols at compile-time.
5+
// Often these protocols are finite-state machines.
6+
7+
// There are several rules for valid HTML12. We’re going to focus on the following:
8+
9+
// Within the html tag there can only be a head and a body tag, in that order.
10+
// Within the head tag there must be exactly one title, and there can be any other
11+
// number of allowed tags (of which we’re only going to model link).
12+
// Within the body there can be any number of allowed tags
13+
// (of which we are only going to model h1 and p).
14+
sealed trait StructureState
15+
trait Empty extends StructureState
16+
trait InHead extends StructureState
17+
trait InBody extends StructureState
18+
19+
sealed trait TitleState
20+
trait WithoutTitle extends TitleState
21+
trait WithTitle extends TitleState
22+
23+
// Not a case class so external users cannot copy it
24+
// and break invariants
25+
final class Html[S <: StructureState, T <: TitleState](
26+
head: Vector[String],
27+
body: Vector[String]
28+
):
29+
// Head tags ---------------------------------------------
30+
31+
def head(using S =:= Empty): Html[InHead, WithoutTitle] =
32+
Html(head, body)
33+
34+
def title(
35+
text: String
36+
)(using S =:= InHead, T =:= WithoutTitle): Html[InHead, WithTitle] =
37+
Html(head :+ s"<title>$text</title>", this.body)
38+
39+
def link(rel: String, href: String)(using S =:= InHead): Html[InHead, T] =
40+
Html(head :+ s"<link rel=\"$rel\" href=\"$href\"/>", body)
41+
42+
// Body tags ---------------------------------------------
43+
44+
def body(using S =:= InHead, T =:= WithTitle): Html[InBody, WithTitle] =
45+
Html(head, body)
46+
47+
def h1(text: String)(using S =:= InBody): Html[InBody, T] =
48+
Html(head, body :+ s"<h1>$text</h1>")
49+
50+
def p(text: String)(using S =:= InBody): Html[InBody, T] =
51+
Html(head, body :+ s"<p>$text</p>")
52+
53+
// Interpreter ------------------------------------------
54+
55+
override def toString(): String =
56+
val h = head.mkString(" <head>\n ", "\n ", "\n </head>")
57+
val b = body.mkString(" <body>\n ", "\n ", "\n </body>")
58+
59+
s"\n<html>\n$h\n$b\n</html>"
60+
61+
object Html:
62+
val empty: Html[Empty, WithoutTitle] = Html(Vector.empty, Vector.empty)

ch13/src/Html2.scala

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package ch13.html2
2+
3+
// Exercise: HTML API Design
4+
sealed trait StructureState
5+
trait NeedsHead extends StructureState
6+
trait NeedsBody extends StructureState
7+
trait Complete extends StructureState
8+
9+
sealed trait TitleState
10+
trait WithoutTitle extends TitleState
11+
trait WithTitle extends TitleState
12+
13+
final class Head[S <: TitleState](contents: Vector[String]):
14+
def title(text: String)(using S =:= WithoutTitle): Head[WithTitle] =
15+
Head(contents :+ s"<title>$text</title>")
16+
17+
def link(rel: String, href: String): Head[S] =
18+
Head(contents :+ s"<link rel=\"$rel\" href=\"$href\"/>")
19+
20+
override def toString(): String =
21+
contents.mkString(" <head>\n ", "\n ", "\n </head>")
22+
object Head:
23+
val empty: Head[WithoutTitle] = Head(Vector.empty)
24+
25+
final class Body(contents: Vector[String]):
26+
def h1(text: String): Body =
27+
Body(contents :+ s"<h1>$text</h1>")
28+
29+
def p(text: String): Body =
30+
Body(contents :+ s"<p>$text</p>")
31+
32+
override def toString(): String =
33+
contents.mkString(" <body>\n ", "\n ", "\n </body>")
34+
object Body:
35+
val empty: Body = Body(Vector.empty)
36+
37+
final class Html[S <: StructureState](
38+
head: Head[?],
39+
body: Body
40+
):
41+
def head(f: Head[WithoutTitle] => Head[WithTitle])(using
42+
S =:= NeedsHead
43+
): Html[NeedsBody] =
44+
Html(f(Head.empty), body)
45+
46+
def body(f: Body => Body)(using S =:= NeedsBody): Html[Complete] =
47+
Html(head, f(Body.empty))
48+
49+
override def toString(): String =
50+
s"\n<html>\n${head.toString()}\n${body.toString()}\n</html>"
51+
52+
object Html:
53+
val empty: Html[NeedsHead] = Html(Head.empty, Body.empty)

ch13/src/ch13.worksheet.sc

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
13.2 Indexed Codata
3+
The basic idea of indexed codata is to prevent methods being called unless certain conditions,
4+
encoded in types, are met. More precisely, methods are guarded by type equalities that callers
5+
must prove they satisfy to call a method. The contextual abstraction features, given instances
6+
and using clauses, are used to implement this in Scala.
7+
*/
8+
trait On
9+
trait Off
10+
11+
trait Switch[A]:
12+
def on(using ev: A =:= Off): Switch[On]
13+
def off(using ev: A =:= On): Switch[Off]
14+
15+
final case class SimpleSwitch[A]() extends Switch[A]:
16+
/*
17+
If a given instance A =:= B exists, then the type A is equal to the type B.
18+
We never create these instances ourselves. Instead the compiler creates them for us.
19+
In the on method, we are asking the compiler to construct an instance A =:= Off,
20+
which can only be done if A is Off. This in turn means we can only call the method
21+
when the Switch is Off. This is the core idea of indexed codata: we reflect states as types,
22+
and restrict method calls to a subset of states.
23+
*/
24+
def on(using ev: A =:= Off): Switch[On] =
25+
SimpleSwitch()
26+
def off(using ev: A =:= On): Switch[Off] =
27+
SimpleSwitch()
28+
29+
object SimpleSwitch:
30+
val on: Switch[On] = SimpleSwitch()
31+
val off: Switch[Off] = SimpleSwitch()
32+
33+
SimpleSwitch.on.off
34+
SimpleSwitch.off.on
35+
36+
// Doesn't compile.
37+
// SimpleSwitch.on.on
38+
39+
// Exercise: Torque
40+
// Below is the definition of Length we previously used. Your mission is to:
41+
42+
// 1. implement a type Force, parameterized by a phantom type that represents the units of force;
43+
// 2. implement a type Torque, parameterized by a phantom type that represents the units of torque;
44+
// 3. define types Newtons and NewtonMetres to represent force in SI units;
45+
// 4. implement a method * on Force that accepts a Length and returns a Torque. It can only be
46+
// called if the Force is in Newtons and the Length is in Metres. In this case the Torque is in
47+
// NewtonMetres. (Torque is force times length.)
48+
49+
trait Newtons
50+
trait Metres
51+
trait NewtonMetres
52+
53+
final case class Length[Unit](value: Double):
54+
def +(that: Length[Unit]): Length[Unit] =
55+
Length[Unit](this.value + that.value)
56+
57+
final case class Torque[Unit](value: Double)
58+
59+
final case class Force[Unit](value: Double):
60+
def *[L](length: Length[L])(using Unit =:= Newtons, L =:= Metres): Torque[NewtonMetres] =
61+
Torque(this.value * length.value)
62+
63+
ch13.Html.empty.head
64+
.link("stylesheet", "styles.css")
65+
.title("Our Amazing Webpage")
66+
.body
67+
.h1("Where Amazing Exists")
68+
.p("Right here")
69+
.toString
70+
71+
// Doesn't compile.
72+
// Html.empty.head
73+
// .link("stylesheet", "styles.css")
74+
// .body
75+
// .h1("This Shouldn't Work")
76+
77+
ch13.html2.Html.empty
78+
.head(_.title("Our Amazing Webpage"))
79+
.body(_.h1("Where Amazing Happens").p("Right here"))
80+
.toString()
81+
82+
// 13.2.2 Beyond Equality Constraints
83+
/*
84+
We can use <:< for evidence of a subtyping relationship,
85+
and NotGiven for evidence that no given instance exists
86+
(with which we can test that types are not equal, for example).
87+
Beyond that, we can view any given instance as evidence.
88+
*/
89+
trait Feet
90+
trait Pounds
91+
trait PoundsFeet
92+
93+
// An instance exists if A * B = C
94+
trait Multiply[A, B, C]
95+
object Multiply:
96+
given Multiply[Metres, Newtons, NewtonMetres] = new Multiply {}
97+
given Multiply[Feet, Pounds, PoundsFeet] = new Multiply {}
98+
99+
// Exercise: Commutivitiy
100+
// A * B == B * A
101+
given commutative: [A, B, C] => Multiply[A, B, C] => Multiply[B, A, C] =
102+
new Multiply {}
103+
104+
final case class Length2[L](value: Double):
105+
def *[F, T](that: Force2[F])(using Multiply[L, F, T]): Torque[T] =
106+
Torque(this.value * that.value)
107+
108+
final case class Force2[F](value: Double):
109+
def *[L, T](that: Length2[L])(using Multiply[F, L, T]): Torque[T] =
110+
Torque(this.value * that.value)
111+
112+
Length2[Metres](3) * Force2[Newtons](4)
113+
Length2[Feet](3) * Force2[Pounds](4)
114+
// Commutative
115+
Force[Newtons](3) * Length[Metres](4)

0 commit comments

Comments
 (0)