I'm following "Implementing functional languages: a tutorial" by SPJ, and I'm stuck on Exercise 2.18 (page 70), reproduced below. This is in the chapter about a template-instantiation evaluator for the simple lazy functional language described in the book (similar to a mini Miranda/Haskell):
Exercise 2.18. Why is it hard to introduce
case
expressions into the template instantiation machine? (Hint: think about whatinstantiate
would do with acase
expression.)
The tutorial then goes on to cover an implementation of several less-general versions of destructuring structured data: an if
primitive, a casePair
primitive, and a caseList
primitive. I haven't yet done the implementation for this section (Chapter 2 Mark 5), but I don't see why implementing these separately would be significantly easier than implementing a single case
primitive.
The only plausible explanations I can offer is that the most generic case
form is variadic in both number of alternatives (number of tags to match against) and arity (number of arguments to the structured data). All of the above primitives are fixed-arity and have a known number of alternatives. I don't see why this would make implementation significantly more difficult, however.
The instantiation of the case
statement is straightforward:
- Instantiate the scrutinee.
Instantiate the body expression of each alternative. (This may be somewhat wasteful if we substitute in unevaluated branches.)(I notice now this may be a problem, will post in an answer.)- Encapsulate the result in a new node type,
NCase
, where:data Node = NAp Addr Addr | ... | NCase [(Int, [Name], Addr)]
Operationally, the reduction of the case
statement is straightforward.
- Check if the argument is evaluated.
- If not, make it the new stack and push the current stack to the dump. (Similar to evaluating the argument of any primitive.)
- If the argument is evaluated, then search for an alternative with a matching tag.
- If no alternative with a matching tag is found, then throw an error (inexhaustive case branches).
- Instantiate the body of the matching alternative with the environment augmented with the structured data arguments. (E.g., in
case Pack {0, 2} 3 4 in <0> a b -> a + b
, instantiatea + b
with environment[a <- 3, b <- 4]
)
A new node type would likely have to be introduced for case (NCase
) containing the list of alternatives, but that's not too dissuading.
I found a GitHub repository @bollu/timi which seems to implement a template-instantiation evaluator also following this tutorial. There is a section called "Lack of lambda and case", which attributes the lack of a generic case
statement to the following reason:
Case
requires us to have some notion of pattern matching / destructuring which is not present in this machine.
However, in this tutorial there is no notion of pattern-matching; we would simply be matching by tag number (an integer), so I'm not sure if this explanation is valid.
Aside, partly for myself: a very similar question was asked about special treatment for case
statements in the next chapter of the tutorial (concerning G-machines rather than template-instantiation).