On this page:
3.1 Introduction
3.2 Assignment
3.3 Features to Implement
3.3.1 Non-Flat Environments
3.3.2 Multi-Arity Lambda
3.3.3 Objects
3.4 Grammar
3.5 Abstract Syntax
3.6 Starter Code
3.7 What To Hand In

3 Objects🔗

    3.1 Introduction

    3.2 Assignment

    3.3 Features to Implement

      3.3.1 Non-Flat Environments

      3.3.2 Multi-Arity Lambda

      3.3.3 Objects

    3.4 Grammar

    3.5 Abstract Syntax

    3.6 Starter Code

    3.7 What To Hand In

3.1 Introduction🔗

In this assignment you will implement the Standard Model of Objects. Though there are many things that languages with objects disagree about, there is some essential commonality, and (a stripped-down version of) that is what you will build.

3.2 Assignment🔗

You are again given a parser,

parse :: S-Exp -> Expr

which now contains some additional language conveniences. Your task is to extend the function

interp :: Expr -> Value

to include the features described below.

You should implement objects as a new kind of value in the language.

You should consider representing the collection of methods in an object as a Hashof mapping names to the method definitions.

You may find it useful to look up and, if necessary, implement functions like map and zip.

3.3 Features to Implement🔗
3.3.1 Non-Flat Environments🔗

For simplicity, in Interpreter, we had just a single “flat” environment, which worked through functional update. Note that this is not how the environment appears in the Stacker!

From now on, we want you to manage the environment in a way that better represents the reality of programming language implementations: as a sequence of “frames”. Each frame is an association of the local names to their values, and the sequence reflects the nested scopes, with the deepest scope first and farthest scope (such as the global scope) last. This will now match the way the environment looks in the Stacker.

As a matter of implementation in plait, we use lists for the stack and hashes for each frame, so the type of Env will be (Listof (Hashof Symbol Value)). Please be careful to not confuse this sequence of frames—which reflects static scoping—with the run-time stack, which is dynamic. Otherwise you know what kind of scope we’ll get!

3.3.2 Multi-Arity Lambda🔗

Until now, in Paret, Functions took only one argument. Now we generalize them to permit zero or more arguments. A function call (e-app) must have exactly as many arguments as the function declaration (e-lam) has; otherwise, signal an error.

3.3.3 Objects🔗

Objects in Paret are best thought of as a generalization of lambda. That is, like lambdas, they are statically scoped. They generalize lambdas in two ways:
  1. They can have many function bodies. Each one is given a different method name and its corresponding parameters.Correspondingly, lambda can be thought of as having only a single method. Hence, it does not need a name to tell apart the different methods, and can be applied directly.

  2. Every method must have at least one argument, in the first position. It can be called anything (though it is conventional to call it self or this). Its value is the object that was evaluated at the method call (e-call) from which this method was obtained.

In conventional infix syntax, think of this as the difference between writing f(x) and o.m(x). The former is a function application (e-app); the latter is a method call (e-call). In the former case, languages behave as we saw in Interpreter (other than allowing any number of parameters). In the latter case, the Standard Model of Objects dictates that they:
  1. evaluate the o expression,

  2. confirm that it is an object (and error otherwise),

  3. look for m in that object (and error if it doesn’t have a method by that name), then

  4. invoke m with (in this case) two arguments, the value of o as the first argument and the value of x as the second one.

Your interpreter should do the same!

In particular, then, note that that in our design—which matches that of Python—the number of parameters in the method definition must be exactly one more than that in the method call. Anything else should produce an error. (Not all languages have this design. Java, for instance, automatically binds this, so that the method definition and use match up.)

3.4 Grammar🔗

<expr> ::= ...                            # minus function definition and call

         | (lam (<var> ...) <expr>)                          # function definition

         | (<expr> <expr> ...)                               # function call

         | (object [<symbol> (<var> <var> ...) <expr>] ...)  # method definition

         | (call <expr> <symbol> <expr> ...)                 # method call

3.5 Abstract Syntax🔗

The definition of Expr is as follows:

(define-type Expr

  (e-num [value : Number])

  (e-str [value : String])

  (e-bool [value : Boolean])

  (e-op [op : Operator]

        [left : Expr]

        [right : 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])

  (e-obj [methods : (Hashof Symbol Method)])

  (e-call [obj : Expr] [method : Symbol] [args : (Listof Expr)]))

3.6 Starter Code🔗

See GitHub Classroom.

3.7 What To Hand In🔗

You will submit to two Gradescope drops for this assignment:

  • objects.rkt, which should be uploaded to the “Code” drop on Gradescope.

  • objects-tests.rkt, which should be uploaded to the “Tests” drop on Gradescope.