On this page:
7.1 Introduction
7.2 Readings
7.2.1 Directly Useful for the Assignment
7.2.2 Optional Reading
7.3 Assignment
7.3.1 Language Extensions
7.3.2 Lazy Evaluation
7.3.3 Strictness Points
7.4 Grammar
7.5 Abstract Syntax
7.6 Starter Code

7 Lazy🔗

    7.1 Introduction

    7.2 Readings

      7.2.1 Directly Useful for the Assignment

      7.2.2 Optional Reading

    7.3 Assignment

      7.3.1 Language Extensions

      7.3.2 Lazy Evaluation

      7.3.3 Strictness Points

    7.4 Grammar

    7.5 Abstract Syntax

    7.6 Starter Code

7.1 Introduction🔗

For this assignment, you will implement a modified version of Paret (called Lazy Paret) that uses lazy evaluation and supports pairs.

Lazy programming is an idea from the 1970s and 1980s that is finally making it into mainstream programming, e.g., through Java 8’s streams.

7.2 Readings🔗
7.2.1 Directly Useful for the Assignment🔗

To understand laziness from a desugaring perspective, read the DCIC section about streams, which also shows how they can be encoded in an eager language.

Furthermore, to understand laziness from an interpreter perspective, see the Laziness chapter in PLAI 3/e.

7.2.2 Optional Reading🔗

To see more examples and understand the beautiful programming patterns that laziness enables, read this classic paper by John Hughes (not Brown’s Spike), entitled Why Functional Programming Matters.

7.3 Assignment🔗
7.3.1 Language Extensions🔗

Extend the language in two ways:
  1. Allow lam to take zero or more arguments. (If the interpreter you start with is already of this form, then you’re welcome to use it and skip this step! Just make sure it’s a correct interpreter.)

  2. Add pairs, which are defined as follows:

    • (pair f s) constructs a pair with two elements, f and s.

    • (first p) gets the first element of a pair p.

    • (second p) gets the second element of a pair p.

    • (is-pair v) evaluates to true if v evaluates to a pair; otherwise, it evaluates to false.

    Pairs in Lazy Paret are not type homogeneous; that is, both elements of a pair do not have to be the same type.

7.3.2 Lazy Evaluation🔗

Lazy evaluation means that arguments to functions should not be evaluated if they are not used in the body. As a result, function calls and the pair constructor must be lazy: that is, a function must not evaluate its argument if it is not used, and a pair must not evaluate its parts if they are not used. The right-hand side of a let should also be lazy. It is good to have tests that ensure all these are indeed lazy.

To implement this, create a new kind of value, called a computation. A computation contains not a value but an expression, and perhaps other parts needed for this to make sense. At various points, defined below, a computation must become a value, but the essence of laziness is that it does not have to happen before then (and hence maybe never at all).

7.3.3 Strictness Points🔗

Certain parts of the language must, if given a computation, force it to turn into a value. These are called strictness points. We define the following to be strictness points of Lazy Paret:

  • The arguments to operators +, ++, first, second, num=, str=, and is-pair.

  • The conditional expression in an if.

  • The function position of an application.

  • The final, top-level result if any (so that the user sees an answer).

  • Anywhere in the source program that invokes the strict operator.

You will find it useful to define a function, force, that converts computations into values by (effectively) evaluating the expression contained in the computation.

7.4 Grammar🔗

The Lazy Paret grammar is as follows:

<file> ::= <expr> ...

 

<un-op>       ::= first | second | is-pair | strict

<bin-op>      ::= num= | str= | pair | + | ++

 

<expr> ::= <num>

         | <string>

         | <id>

         | true

         | false

         | (if <expr> <expr> <expr>)

 

         | (<bin-op> <expr> <expr>)

         | (<un-op> <expr>)

         | (<extended-op> <expr> <expr> ...+)

 

         | (lam (<id> ...) <expr>)

         | (let ([<id> <expr>] ...) <expr>)

         | (<expr> ...+)

7.5 Abstract Syntax🔗

(define-type Expr

  (e-num [value : Number])

  (e-str [value : String])

  (e-bool [value : Boolean])

  (e-op [op : Operator]

        [left : Expr]

        [right : Expr])

  (e-ext-op [op : Operator]

           [arg : (Listof Expr)])

  (e-un-op [op : UnaryOperator]

           [arg : Expr])

  (e-if [condition : Expr]

        [consq : Expr]

        [altern : Expr])

  (e-lam [params : (Listof Symbol)]

         [body : Expr])

  (e-app [func : Expr]

         [arg : (Listof Expr)])

  (e-var [name : Symbol]))

 

(define-type Operator

  (op-pair)

  (op-plus)

  (op-append)

  (op-num-eq)

  (op-str-eq))

 

(define-type UnaryOperator

  (op-strict)

  (op-first)

  (op-second)

  (op-is-pair))

Your Value definition must include at least these terms:

(define-type Value

  (v-num [value : Number])

  (v-str [value : String])

  (v-bool [value : Boolean])

  (v-fun [param : (Listof Symbol)]

         [body : Expr]

         [env : Env])

  (v-pair [first : Value]

          [second : Value])

  ;; plus anything you need to represent computations

  )

7.6 Starter Code🔗

See GitHub Classroom.