From f68ebcedd848881d099d034af13dbbf5b09ad0ae Mon Sep 17 00:00:00 2001 From: Noel Welsh Date: Fri, 27 Oct 2023 16:44:34 +0100 Subject: [PATCH] Add expression exercise --- src/pages/adt-interpreters/Expression.scala | 32 ++++++ src/pages/adt-interpreters/reification.md | 108 +++++++++++++++++++- 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/pages/adt-interpreters/Expression.scala diff --git a/src/pages/adt-interpreters/Expression.scala b/src/pages/adt-interpreters/Expression.scala new file mode 100644 index 00000000..f8d9c200 --- /dev/null +++ b/src/pages/adt-interpreters/Expression.scala @@ -0,0 +1,32 @@ +enum Expression { + case Literal(value: Double) + case Addition(left: Expression, right: Expression) + case Subtraction(left: Expression, right: Expression) + case Multiplication(left: Expression, right: Expression) + case Division(left: Expression, right: Expression) + + def +(that: Expression): Expression = + Addition(this, that) + + def -(that: Expression): Expression = + Subtraction(this, that) + + def *(that: Expression): Expression = + Multiplication(this, that) + + def /(that: Expression): Expression = + Division(this, that) + + def eval: Double = + this match { + case Literal(value) => value + case Addition(left, right) => left.eval + right.eval + case Subtraction(left, right) => left.eval - right.eval + case Multiplication(left, right) => left.eval * right.eval + case Division(left, right) => left.eval / right.eval + } +} +object Expression { + def apply(value: Double): Expression = + Literal(value) +} diff --git a/src/pages/adt-interpreters/reification.md b/src/pages/adt-interpreters/reification.md index 9d52ec3f..9d9bfe6c 100644 --- a/src/pages/adt-interpreters/reification.md +++ b/src/pages/adt-interpreters/reification.md @@ -25,7 +25,7 @@ There are three different types of methods: This structure is often called an **algebra** in the functional programming world. -## Implementing Interpreters with Reification +### Implementing Interpreters with Reification Now that we understand the components of interpreter we can talk more clearly about the implementation strategy we used. We used a strategy called a **reification**, **deep embedding**, or **initial algebra**. @@ -40,3 +40,109 @@ Here are the rules for reification: 4. Each product type holds exactly the parameters to the constructor or combinator, including the `this` parameter for combinators. If we do this, the interpreter becomes a structural recursion on the algebraic data type we have just defined. + + +### Exercise: Arithmetic {-} + +Now it's your turn to practice using reification. Your task is to implement an interpreter for arithmetic expressions. An expression is: + +- a literal number, which takes a `Double` and produces an `Expression`; +- an addition of two expressions; +- a substraction of two expressions; +- a multiplication of two expressions; or +- a division of two expressions; + +Reify this description as a type `Expression`. + +
+```scala mdoc:silent +enum Expression { + case Literal(value: Double) + case Addition(left: Expression, right: Expression) + case Subtraction(left: Expression, right: Expression) + case Multiplication(left: Expression, right: Expression) + case Division(left: Expression, right: Expression) +} +object Expression { + def apply(value: Double): Expression = + Literal(value) +} +``` +
+ +Now implement an interpreter `eval` that produces a `Double`. This interpreter should interpret the expression using the usual rules of arithmetic. + +
+```scala mdoc:reset:silent +enum Expression { + case Literal(value: Double) + case Addition(left: Expression, right: Expression) + case Subtraction(left: Expression, right: Expression) + case Multiplication(left: Expression, right: Expression) + case Division(left: Expression, right: Expression) + + def eval: Double = + this match { + case Literal(value) => value + case Addition(left, right) => left.eval + right.eval + case Subtraction(left, right) => left.eval - right.eval + case Multiplication(left, right) => left.eval * right.eval + case Division(left, right) => left.eval / right.eval + } +} +object Expression { + def apply(value: Double): Expression = + Literal(value) +} +``` +
+ +Add methods `+`, `-` and so on that make your system a bit nicer to use. Then write some expressions and show that it works as expected. + +
+Here's the complete code. + +```scala mdoc:reset:silent +enum Expression { + case Literal(value: Double) + case Addition(left: Expression, right: Expression) + case Subtraction(left: Expression, right: Expression) + case Multiplication(left: Expression, right: Expression) + case Division(left: Expression, right: Expression) + + def +(that: Expression): Expression = + Addition(this, that) + + def -(that: Expression): Expression = + Subtraction(this, that) + + def *(that: Expression): Expression = + Multiplication(this, that) + + def /(that: Expression): Expression = + Division(this, that) + + def eval: Double = + this match { + case Literal(value) => value + case Addition(left, right) => left.eval + right.eval + case Subtraction(left, right) => left.eval - right.eval + case Multiplication(left, right) => left.eval * right.eval + case Division(left, right) => left.eval / right.eval + } +} +object Expression { + def apply(value: Double): Expression = + Literal(value) +} +``` + +Here's an example showing use, and that the code is correct. + +```scala mdoc:silent +val fortyTwo = ((Expression(15.0) + Expression(5.0)) * Expression(2.0) + Expression(2.0)) / Expression(1.0) +``` +```scala mdoc +fortyTwo.eval +``` +