Dwayne Crooks
Posted on July 9, 2019
After adding addition and subtraction to my calculator I decided to proceed with multiplication.
Due to good separation of concerns it required little change to the code. However, that wasn't going to be the end of it.
After using the calculator for a while I stumbled upon the expression 1+2*3
.
1+2*3=1+6=7
but my calculator gave 9
because it did 1+2*3=3*3=9
. I forgot that my expression evaluator didn't handle operator precedence.
A good opportunity to add a test
I knew improving my expression evaluator would require a non-trivial code change and I knew that I didn't want to be constantly entering expressions via the UI to check if I correctly implemented the evaluator.
So I added a failing test.
operatorPrecedenceSuite : Test
operatorPrecedenceSuite =
describe "operator precedence" <|
[ test "multiplication is done before addition" <|
\_ ->
let
calculator =
Calculator.new
|> Calculator.process (Digit 1)
|> Calculator.process (Operator Plus)
|> Calculator.process (Digit 2)
|> Calculator.process (Operator Times)
|> Calculator.process (Digit 3)
|> Calculator.process Equal
in
calculator
|> Calculator.toDisplay
|> Expect.equal { expr = "1+2*3=7", output = "7" }
]
The shunting yard algorithm
I just implemented enough of the shunting yard algorithm to get addition, subtraction and multiplication working correctly. With plans to add division in the near future.
Here's the code if you're interested in the implementation details.
The operatorPrecedenceSuite
test passed and I was elated.
Elm handled the complexity well
Initially the expression evaluator was hidden inside the Calculator
module. But the complexity of the evaluator got to the point where I wanted to ensure it was correct without having to go through the Calculator
's public API.
To handle the complexity I factored the evaluator into its own module, Expr, with the following public API:
module Expr exposing (Expr(..), eval, toString)
This allowed me to extensively test it. For e.g. here's one of the tests I added:
test "1+2*3" <|
\_ ->
Expr.eval (Add (Const 1) (Mul (Const 2) (Const 3)))
|> Expect.equal 7
You can view the full suite of tests here.
It was simple
Even though I ended up writing a lot of extra code I still felt that Elm managed the complexity well. I still understand how my code works, I know where I need to make changes when I need to add a new feature and the types along with the tests make me feel extremely confident in my implementation.
Though adding multiplication to the calculator wasn't easy. Elm made it simple.
Posted on July 9, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.