3 OMac
3.1 Introduction
In the Macros assignment, we learned how to implement Racket macros through a series of exercises. In this assignment, we’ll build a more complex and interesting series of object systems using Racket macros yet again (hence the name “OMac”).
3.2 Assignment
This assignment consists of three parts: defining objects, defining classes, and a short analysis question. All code for this assignment will be written in #lang racket (though you are welcome to use the SMoL compatibility layers).
3.2.1 Part 1: Objects
- object, representing an object containing fields and methods
(object (fields [fn iv] ...)
(methods [mn imp] ...))
where fn is a field name (symbol), mn is a method name (also a symbol), and iv (“initial value”) and imp (“implementation”) are both expressions.All methods are explicitly declared as functions (typically defined using a lambda in place, but the function could also be defined outside and referenced from inside the object).The methods may be evaluated when the object is created or each time the method is called, but all other values (like initial values for fields) should be evaluated immediately.You may assume there is no overlap between field names and method names.Finally, all methods must take at least one argument. This argument will be the object itself. Conventionally, this argument would be called self or this (but it doesn’t have to be). The call macro is responsible for passing the object along as the first parameter.
There are generally two schools of thought on handling the self argument. In Java, the name this is bound automatically, and does not show up in the headers. In contrast, in Python, the name is not chosen by the language, and the programmer explicitly chooses it. We could design our object macro to automatically bind a name, too. There are multiple ways of doing it in Racket using features slightly more advanced than what we’ve used in this course. The latter design arguably has less “magic”. However, it means that a method declaration always has one more field than a corresponding call to the same method. Relatedly, it may actually be more confusing to students.
- call, used to call methods on an object:
(call o mn args ...)
where o is an object, mn is a method name (a symbol), and args ... are arguments to that method (all expressions).If o does not have a method by the name of mn, you should raise an error. Our stencil provides a function raise-method-not-found-exception for doing this, which you should use by passing raise-method-not-found-exception the invalid method name. Missing field errors should be handled directly by Racket.Note: call’s specification enforces that only methods are accessible from outside an object. Fields are effectively private, and can only be referenced by methods from inside the object.
3.2.1.1 Internal Keywords
#lang racket (define-syntax foo (syntax-rules (bar baz) [(_ [bar x ...] [baz y ...]) (begin (println "all the bars") (println x) ... (println "all the bazs") (println y) ...)]))
#lang racket (foo [bar 1 2] [baz 3]) (foo [bar] [baz])
#lang racket (foo [baz 3]) (foo [bar]) (foo [1 2] [3 4 5])
3.2.1.2 Making symbols from expressions in identifiers
#lang racket (define-syntax make-symbol (syntax-rules () [(_ sym) 'sym])) (make-symbol hi) ; returns 'hi
3.2.1.3 Advice on Object Implementation
It might help to think of the object form as being roughly analogous to a JavaScript literal object (JSON).
3.2.1.4 How to Use Structs
#lang racket (struct <struct-id> (<field-id> ...))
#lang racket (struct card (suit value))
#lang racket (<struct-id> <field-val> ...)
#lang racket (define four-of-hearts (card "hearts" 4))
#lang racket (card? four-of-hearts) ; #t (card? "hello") ; #f
#lang racket (card-suit four-of-hearts) ; "hearts" (card-value four-of-hearts) ; 4
3.2.1.5 Example
#lang racket (define cowboy-object (object (fields [name "Timmy the Cowboy"]) (methods [say-howdy-to (lambda (self to) (string-append name " says: Howdy " to "!"))] [get-name (lambda (self) name)] [set-name (lambda (self x) (set! name x))]))) (call cowboy-object say-howdy-to "Partner") ; returns "Timmy the Cowboy says: Howdy Partner!"
3.2.1.6 Starter Code
Please read the Testing Guidelines for guidelines on how to write tests for the Implementation assignments.
3.2.2 Part 2: Classes
#lang racket (class class-name (fields [fn iv] ...) (methods [mn imp] ...)) (new class-name)
#lang racket (class Cowboy (fields [name "Timmy the Cowboy"]) (methods [say-howdy-to (lambda (self to) (string-append name " says: Howdy " to "!"))] [get-name (lambda (self) name)] [set-name (lambda (self x) (set! name x))])) (new Cowboy)
3.2.2.1 Starter Code
If you are using your object macro in Part 2: do not import your object macro directly from objects.rkt. Instead, you should copy your object definition into your classes.rkt file. This is necessary for autograding purposes.
3.2.3 Part 3: Question
3.3 What to Hand In
objects.rkt, which should be uploaded to the “Objects - Code” drop on Gradescope.
classes.rkt, which should be uploaded to the “Classes - Code” drop on Gradescope.
objects-tests.rkt, which should be uploaded to the “Objects - Tests” drop on Gradescope.
classes-tests.rkt, which should be uploaded to the “Classes - Tests” drop on Gradescope.