Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Types and Programming Languages Chapter 4 An ML Implementation of Arithmetic Expressions
Clone this wiki locally
## Our implementations
Before the meeting, a large number of people implemented code based on the material in chapters 3 and 4:
- @beng’s Swift implementation
- @urbanautomaton’s Prolog and Ruby implementations
- @mudge’s Rust implementation
- @leocassarani’s Haskell implementation
- @dkandalov’s Kotlin implementation
- @richardcooper’s Python implementation
- @tuzz’s Ruby implementation
- @tuzz’s Turing Machine implementation
- @lsrus’s Racket implementation
- @zetter’s Elixir implementation
- @SimonWoolf’s Elixir implementation
- @tomstuart’s Ruby implementation
- @jcinnamond’s Parslet implementation
- @h-lame’s Ruby implementation
- @eightbitraptor’s Scheme implementation
We started by discussing what we had nominally planned for the meeting. We were going to mob program an implementation of the
eval function from chapter 4. We agreed we should reserve plenty of time (a full hour) for show and tell as lots of people had built things.
We decided we'd like to implement
eval by figuring things out for ourselves, rather than basing it on the implementation in the book. We started by considering the boolean rules. Tom wrote them up on the whiteboard:
t ::= true false if t then t else t if true then t₂ else t₃ → t₂ if false then t₂ else t₃ → t₃ t₁ → t₁′ ---------------------------------------------- if t₁ then t₂ else t₃ → if t₁′ then t₂ else t₃
We collectively came up with some test cases relating to these rules, e.g.
if true then true else false. We'd use these test cases to drive out a solution in a TDD-esque manner. Paul offered to type.
We decided to use CodePen and opted for some low-fi
How should we represent terms? Functions? Objects? Arrays?
We agreed to use constructor functions for a number of reasons, including:
- it would be easier to add methods to our objects if we decided to
- it feels quite natural to think in terms of objects in our programs (Murray)
- it would be easier to reference their properties, e.g.
Implementing the rules
We focussed on the
eval1 function which performs a small-step evaluation of a term. We spoke about how to handle the "no rule applies" case. We agreed to come back to this once we had developed our code a bit further.
We then moved on to
eval which needed to call
eval1 multiple times. We had two options:
We can either use an explicit looping construct such as a while loop or we could use recursion.
Recursion seemed to be a popular choice. Paul sketched out the code and Leo explained how it worked.
We added more rules to implement, this time including
iszero. We still needed to deal with the "no rule applies" problem but decided to keep going. We didn't really have enough implemented to be able to construct good test cases for this yet.
There was a brief interlude. It was generally agreed that adding salt to coffee was a bad idea. Someone pointed out that people seem to add almost anything to coffee, e.g. butter and rum. @tuzz proposed banoffee coffee and toffee coffee. Everyone
rolled their eyes applauded profusely.
We briefly talked about whether we should return a new
Zero object for
E-PredZero or return the existing
Zero object from
t1. This was quickly cleared up by following the rule literally and returning a new
We proceeded to fill in the remaining rules. We were approaching an hour into the meeting so we made the decision to park the implementation in its current state. Things that were left:
- adding guard conditions around nested
eval1calls for things where "no rule applies"
isNumericValand adding it as a condition to the appropriate rules e.g.
(pred (succ true))
A cleaned up version of our code is available on GitHub.
Show and tell
(The implementations are listed at the top of this page.)
Ben used Swift to implement the
eval function. He used
indirect enum in a few places. He noted it was more-or-less a literal translation of the code in the book. He used pattern matching in his case statement. There was a brief discussion about the use of
where in the case statement. Ben explained that
where is part of Swift's pattern matching and can be used outside of
case as well. He wasn't sure whether it was idiomatic Swift. He suggested that option types might be more idiomatic.
Simon used Prolog. There was some discussion about premises being written after the rule. This differs from the style in the book. His implementation referred to
reduce which some felt was a better name for this function. Simon pointed out that Prolog lends itself well to this kind of problem. So well, in fact, that we're able to run the program backwards to generate terms:
Leo pointed out this this "drives the point home" that we're dealing with an infinite set. We can continually apply the rules to build bigger terms.
Paul's implementation was in Rust. He hoped, before he started, that Rust would be a good language for this as it supports pattern matching but then 'immediately ran into problems'. Specifically, problems relating to the ownership of objects. We noted that
indirect from Ben's Swift implementation was similar to the use of
box in Paul's. This is a way to tell the static compiler to use the heap rather than the stack for storing terms. By default, Rust prefers the stack. Paul made use of the
option monad to handle
eval1 calls on terms that couldn't be evaluated any further.
Dmitry used Kotlin which he explained is a relatively new language that's related to Scala and Java. He didn't go into too much detail, but explained he had implemented the
eval function as well.
Leo used Haskell and pointed out it was a straightforward implementation (no need for 'indirect' or 'box'). He implemented the
eval function which used the small-step semantics. He then explained he'd experimented with QuickCheck, which is a property testing framework. He took the theorems from chapter 3 and generated random terms which he fed through these property tests.
In cases where a violation of some property was found, Leo implemented a custom shrinking function which attempts to find the simplest possible counter-example that demonstrates the problem. He pointed out that you don't have to write your own shrinking function. He'd had a go at implementing the
E-Funny rules from chapter 3 to demonstrate that they violated some of the theorems. He also implemented
eval using big-step semantics then used QuickCheck to demonstrate that the two implementations were equivalent.
Rich's implementation was in Python. He explained it was similar to the other implementations. He explicitly threw exceptions and used an explicit while loop rather than recursion for the
eval function. He implemented a constructor function to build Term classes and explained it was a bit unusual to define classes inside a function. He then refactored his code to use functions for each rule rather than the case statement.
Rich implemented the 'funny' rules as well and showed they violated the theorems. He then spoke about how he used
yield to replace the explicit error handling. In cases where no further evaluation could take place, nothing would be yielded (an analogy was made to an empty list). He then moved on to attempting to write a pattern matching framework in Python.
Finally, Rich explained that he'd used Jupyter which is a tool that is commonly used when scientists work with large data sets. It allows you to evaluate expressions in real time and display their derivation trees. He'd used nested html tables for this. The
isNumerivVal condition became just another rule in these derivation trees.
Laura used Racket. She was hesitant at first to demo her implementation, but Leo was keen to see some Racket code. She explained she'd used Racket's pattern matching features as well as guard clauses.
Chris had implemented
eval in Ruby and took the book's implementation quite literally. He explained he'd have liked to implement a polymorphic solution which moved behaviour onto the term subclasses. He briefly showed a graph that represents his Turing machine implementation.
Chris had implemented the rules for a Turing machine that evaluated terms. He explained the online environment he'd used only supported single-letter symbols which made it quite difficult to make sense of. He showed the Turing machine running to reduce a non-trivial expression to
He explained that his Turing machine operated in a right-left direction of the input. Much of the implementation was taken up with the monotony of copying symbols around on the tape. He demonstrated an example that used lots of
succ and showed this copying process.
Chris used Elixir and had made use of its pattern matching features and guard clauses. He'd tried to change
eval to work without throwing exceptions.
Simon also used Elixir with a very simple lexer and parser to parse a string input into nested tuples, and a small step and big step evaluator. Each used a different workaround to the problem in Elixir of not being able to have arbitrary recursive functions in guard clauses for is_numeric etc. (the small-step capped the maximum recursion depth; the big-step used a custom conditional macro rather than the built-in case clause).
Tom had implemented a parser and evaluator for the metalanguage used in the book for defining grammars. He'd then used this metalanguage to define the specific grammar defined in chapters 3 and 4. Like Rich, he needed to define a syntactic category for
We discussed the possibility of using Tom's metalanguage in future meetings for other grammars the book defines. He suggested it might just work without additional changes.
John had implemented
eval in Ruby through the use of parslet. He explained that he'd exploited parslet's support for transforming trees in order to actually perform the evaluation of expressions.
At the end of the meeting, we held a very brief retrospective as we'd already overrun. We agreed we should:
- timebox show and tells so that everyone has an equal amount of time to present
- be more prepared for mobbing, e.g. set up a simple test harness ahead of time
We questioned whether mob programming will work with more complicated chapters. John noted that we'll likely be ok for a little while longer as the next chapters are about the lambda calculus, which we collectively have some knowledge of.
Tom explained that he felt encouraged by this meeting. It's a challenging book and we seem to have collectively got a grip on the main concepts.
Finally, we talked about when to hold the next meeting as some people are away. Deferred to Slack.
A few of us went to the pub.