Recall our boa, armadillo, and animal data definitions from a couple of weeks ago:
;; A boa is a (make-boa symbol number symbol)
(define-struct boa (name length food))
;; An armadillo is a (make-armadillo number boolean)
(define-struct armadillo (length dead?))
;; run-over : armadillo -> armadillo
;; consume an armadillo and return a new one that is dead and is one
;; unit longer than the input armadillo
(define (run-over adillo)
(make-armadillo (+ (armadillo-length adillo) 1)
true))
;; An animal is either
;; - a boa, or
;; - a dillo
;; longer-than? : animal number -> boolean
;; determine whether an animal is longer than a given length
(define (longer-than? ani len)
(cond [(boa? ani) (> (boa-length ani) len)]
[(armadillo? ani) (> (armadillo-length ani) len)]))
Let's turn boas and dillos into objects instead of structs:
(define make-boa-obj
(lambda (name length food)
(local [(define (longer-than? len) (> length len))]
(lambda (service)
(cond [(symbol=? service 'longer-than?) longer-than])))))
(define make-dillo-obj
(lambda (length dead?)
(local [(define (longer-than? len) (> length len))]
(lambda (service)
(cond [(symbol=? service 'longer-than?) longer-than]
[(symbol=? service 'run-over)
(make-dillo-obj (+ length 1) true)])))))
Several things to note:
This example shows the natural, direct mapping between the style of code we've been writing in Scheme and OO code. You can take any data definition from this term and turn it into code with objects by:
Moral: functional and OO programs have the same style and structure -- they just organize their code a bit differently! This means that everything you've learned about templates and data definitions in 2135 carries over and applies to programming in mainstream OO languages.
Why didn't we use set! in run-over in the armadillo object?
Let's edit the code to take this approach:
(define make-dillo-obj
(lambda (init-length init-dead?)
(local [(define (longer-than? len) (> length len))
(define length init-length)
(define dead? init-dead?)]
(lambda (service)
(cond [(symbol=? service 'longer-than?) longer-than]
[(symbol=? service 'run-over)
(begin
(set! length (+ length 1))
(set! dead? true))])))))
We need the defines for length and dead? in the local so that we maintain our rule of only using set! on local variables. Note that this version changes the contract of run-over, which used to return an armadillo, and now returns void (our current implementation of objects doesn't let us return the current dillo in run-over).
This code also doesn't match the original (functional) code, which returned a new dillo and left the old one intact. Recall that the goal of functional programming is to have functions return values, not to change existing data. Changes in existing data can be dangerous in large-scale projects, because someone else may try to use your original dillo, not expecting its values to change; this is much like the cookies version of the code I posted yesterday, where changes on one page accidentally affect another.
In general, assignment is abused in programming, and complicates lots of analyses we might want to do with programs. We don't have time in 2135 to go into these problems in detail, and I can't explain them effectively in a sentence. So, the answer for now is that the set! version isn't consistent with how we've been programming all term, it does introduce some dangers in the big picture, and you'll just have to trust me on that for now. You'd write the set! version in C++ since it doesn't have garbage collection; Java is designed to be a lot like Scheme (despite the syntax), so the non-set! version is quite appropriate in Java.
Is there any way to avoid the garbage collection feature of Scheme? I was looking through today's notes and was wondering how to change the program to keep the old dillo. Suppose we just want to clone a new dillo that is exactly the same as the original but a foot longer. From my understanding of the lecture today, if we just modify today's code, the old dillo will be deleted. Any way to get around this?
See the previous question for the code that changes the current dillo.
The garbage collection isn't the problem here (and no, you can't---nor would you want to---turn it off). Garbage collection simply means "when you have stopped using a value, the language will free its memory for you". As long as some part of your program still refers to the original dillo, it won't get deleted.
Our in-class version does not throw away the old dillo! Matter of fact, it keeps the old dillo intact, and just creates a new one with the new data. It's the set! version that effectively throws away the old dillo, because once you change the length/dead data, you can't get the old data back.
I'm not sure what you mean by "clone" in your question, since I see clone as "create a new one that's mostly the same with a different length". That's the approach our in-class version takes: it clones, while the other version destroys the original.
I've noticed that in labs and homework we've had to determine whether a use of set! is justified. I was just wondering, is it only justified if there are no alternatives? (In yesterday's code for the armadillo, we did not use set! to change the original armadillo, but instead just made a new one, but would the use of set! still be justified there?)
For purposes of the homework and exams, follow the two rules from lecture to justify set!:
Technically, the set! version of armadillo in the notes from yesterday's class respects both of these rules, so it could be justified. There are still cases where it's better to create the new object than change the old one, but we haven't had time to discuss that this term. You won't be asked to make "judgement calls" like this on the exam. You'd simply be asked to apply the two rules above.
|
This page maintained by Kathi Fisler Department of Computer Science Worcester Polytechnic Institute |