In the Macros assignment, we learned how to implement Racket macros through a series of toy 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”).
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).
- 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).Also, 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.
#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])
(struct struct-id (field-id ...))
(struct card (suit value))
(struct-id field-val ...)
(define four-of-hearts (card "hearts" 4))
(card? four-of-hearts) ; #t
(card? "hello") ; #f
(card-suit four-of-hearts) ; "hearts"
(card-value four-of-hearts) ; 4
#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!"
Please read the Testing Guidelines for guidelines on how to write tests for the Implementation assignments.
#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)
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.
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.