MIT Scheme Message Passing Abstraction - scheme

In a Computer Science course I am taking, for homework, we were tasked with several different questions all pertaining to message passing. I have been able to solve all but one, which asks for the following:
Write a mailman object factory (make-mailman) that takes in no parameters and
returns a message-passing object that responds to the following messages:
'add-to-route: return a procedure that takes in an arbitrary number of mailbox objects
and adds them to the mailman object's “route”
'collect-letters: return a procedure that takes in an arbitrary number of letter objects and
collects them for future distribution
'distribute: add each of the collected letters to the mailbox on the mailman's route whose
address matches the letter's destination and return a list of any letters whose destinations
did not match any mailboxes on the route (Note: After each passing of
'distribute
the
mailman object should have no collected letters.)
Some remarks that are given to make the code easier include:
If multiple letters are distributed to the same mailbox in one distribution round, any one
of them may be the “latest” letter whose message is returned by passing 'get-latest-message
to the mailbox.
No two mailboxes will have the same address.
No mailbox or letter will be passed to the mailman more than once.
The bad letters returned by distribute do not need to be in a specific order.
Use the . args syntax for accepting arbitrary amount of arguments.
This is what I have been able to figure out for myself:
(define (make-mailman)
(let ((T '()))
(define (route-adder . mobjects)
(assoc mobjects T))
(define (letter-collecter . lobjects)
(assoc lobjects T))
(define (add-to-route mobjects)
(begin (set! T (cons (route-adder . mobjects) T)) 'done))
(define (collect-letters lobjects)
(begin (set! T (cons (sort-strings (letter-collecter . lobjects)) T)) 'done))
(define (dispatch z)
(cond ((eq? z 'add-to-route) add-to-route)
((eq? z 'collect-letters) collect-letters)
((eq? z 'distribute) "unsure of what to do here")
(else "Invalid option")))
dispatch))
Any help that can be given to me here will be appreciated, as I have tried looking at this problem for a while, and cannot figure out what to do from here.

Your code has all kinds of mix-ups. :) Let's proceed step by step.
The dispatch bit is almost OK:
(define (make-mailman)
(let ...
...
(define (dispatch msg) ;; use short but suggestive var names
(cond
((eq? msg 'add-to-route) add-to-route)
((eq? msg 'collect-letters) collect-letters)
((eq? msg 'distribute)
;; "unsure of what to do here" <<-- Distribute the letters, what else?
distribute-the-letters)
(else "Invalid option")))
dispatch))
With such objects, a sample call will be (define ob (make-mailman)) and then ((ob 'add-to-route) box1 box2 ... boxn) etc. So add-to-route procedure must be defined this way:
(define (make-mailman)
(let ((self (list '(ROUTE) ; each mailman has a route, and a mailbag
'(MAILBAG)))) ; use suggestive name here (T, what T?)
...
(define (add-to-route . mailboxes)
(let ((route (assoc 'ROUTE self)))
(set-cdr! route
(append mailboxes ; there will be no duplicates
(cdr route)))
'DONE))
Right? Same with the letters:
(define (collect-letters . letters)
(let ((mailbag (assoc 'MAILBAG self)))
.....
'DONE))
Now we can deal with the missing part, distribute-the-letters:
(define (distribute-the-letters)
;; for each letter in my mailbag
(let* ((mailbag (assoc 'MAILBAG self))
(mailboxes (cdr (assoc 'ROUTE self)))
(letters (cdr mailbag)))
(if (null? letters) ()
(let loop ((letter (car letters))
(letters (cdr letters))
(not-delivered ()))
;; access its address,
(let* ((address (letter 'get-address))
;; (we assume it supports this interface,
;; or maybe that's part of a previous assignment)
;; and find a mailbox on my route such that
(mbx (find-mailbox address mailboxes)))
;; its address matches the letter's
;; and if so,
(if .....
;; put that letter into this mailbox:
((mbx 'put-letter) letter)
;; (we assume it supports this interface,
;; or maybe that's part of a previous assignment)
;; but if not, add letter to the "not-delivered" list
..... )
(if (null? letters)
;; having emptied the mailbag, return the "not-delivered" list
(begin (set-cdr! mailbag nil) not-delivered)
(loop (car letters) (cdr letters) not-delivered)))))))
We assume that both letter and mailbox objects support the message type 'get-address to which they both return the same comparable address type of object, and that mailbox objects support 'put-letter message.

Other than the specifics of the message functionality, it looks like you've nailed it. There are however some errors:
This (route-adder . mobjects) should be (router-adder objects) and similarly for (letter-collector . lobjects).
The use of begin is unneeded. The body of a (define (func . args) <body> ...) is implicitly enclosed in a begin.
Idiomatically your code could be written as:
(define (make-mailman)
(let ((T '()))
;; ...
(lambda (z)
(case z
((add-to-route) add-to-route)
((collect-letters) collect-letters)
((distribute) distribute)
(else (error "Invalid option"))))))
[but you may not know about case nor lambda yet...]
As for solving the actual messaging functionality. You are going to need to maintain a set of mailboxes where each mailbox is going to hold a set of letters. A letter will presumably consist of an address and some content (extra credit for a return-address). The distribute behavior will check the address on each letter and deposit it in its mailbox. The mailman will need to hold letters (while on his route collecting-letters) until instructed to distribute.
For this you might start by building up the lower-levels of the functionality and then using the lower-levels to build up the actual message passing functionality. Starting like, for example:
(define (make-letter addr content)
`(LETTER ,addr ,content))
(define letter-addr cadr)
;; ...
(define (make-mailbox addr)
'(MBOX ,addr))
(define mailbox-letters cddr)
(define (mailbox-letters-add mailbox letter)
(set-cdr! (cdr mailbox) (cons letter (mailbox-letters mailbox))))
;;...

Related

User input to a list

I'm trying to take in user input and add it to a list but I have not been able to get it working. I'm still new to scheme and have been browsing around to try to figure it out but I haven't had any luck.
(display "Continue to enter numbers until satisfied then enter e to end")
(newline)
(define (intlist number)
(define number(read-line))
(cond (number? number)
(cons lst (number))
(else
(display lst)
done')))
this is what I have so far. Any help or direction to where I can learn a bit more is appreciated.
Your solution is almost correct, but it doesn't work, because:
Variable lst doesn't exist and with this expression (number), you are calling some undefined function number.
done' is badly written 'done.
Function cons expects element as first argument and other element or list as second argument.
See these examples:
> (cons 1 2)
'(1 . 2)
> (cons 1 '())
'(1)
> (cons 1 (cons 2 (cons 3 '())))
'(1 2 3)
Last example is important here- your function will be recursive and it will return a cons cell in each step. If I will follow your solution, this can be enough:
(define (list-from-user)
(let ((number (read)))
(if (number? number)
(cons number (list-from-user))
'())))
(Note that I used read instead of read-line, because read-line returns string, and let instead of define.)
If you really want to wait for e, you must decide, what happens if user enters something that isn't number and isn't e- maybe just ignore it?
(define (list-from-user)
(let ((user-input (read)))
(cond ((number? user-input) (cons user-input (list-from-user)))
((eq? user-input 'e) '())
(else (list-from-user)))))
Then just add some wrapping function with output:
(define (my-fn)
(begin (display "Continue to enter numbers until satisfied then enter e to end")
(newline)
(list-from-user)))
and call it
> (my-fn)
Note that my function returns list with numbers, instead of some useless 'done, so I can use that function in other functions.
(define (sum-of-list)
(let ((lst (my-fn)))
(format "Sum of given list is ~a." (apply + lst))))
> (sum-of-list)

General memoization in Scheme

i have been assigned homework to make a general memoization procedure in scheme, so far it works on procedures that take one argument, but fail on what it seems to be the last argument when provided with more than 1. It also fails to memoize procedures that take no arguments.
Any help would be greatly appreciated.
(define mem
(lambda (mem-it func)
(let ((table (make-table) )(func-store func))
(cond
((equal? mem-it 'memoize)
(lambda args
(if (null? args)
func
(let ((prev (lookup args table)))
(or prev
(let ((result (apply func args)))
(insert! args result table)
result))))))
((equal? mem-it 'unmemoize)
(func-store))
(else (display "No Such command"))))))
This is what i have so far
(define (test-proc . args)
(display "computing test-proc of ")
(display args)
(newline)
(if (null? args)
0
(+ (expt (- 42 (car args)) 2)
(apply test-proc (cdr args)))))
And here is the test procedure provided
The error occurs when i try to run the following test
(set! test-proc (mem 'memoize test-proc))
(test-proc 40 41 42 43 44)
Here are the other procedures used
(define (make-table)
(list '*table*))
(define (lookup key table)
(let ((record (assoc key (cdr table))))
(and record (cdr record))))
(define (insert! key value table)
(let ((record (assoc key (cdr table))))
(if record
(set-cdr! record value)
(set-cdr! table
(cons (cons key value) (cdr table))))))
Your memoizarion procedure has a feature where it returns the implementation procedure when no arguments are passed:
((mem 'memoize test-proc)) ; ==> test-proc
The base case of your test procedure will never hit because of this feature thus for (test-proc 1) you can substitute it with the expression (+ 1681 test-proc) which will signal an error since test-proc is not a number.
It's better to use unique magic values:
(define +GET-PROC+ (list "get-proc"))
(test-proc +GET-PROC+) ; ==> original-test-proc
Since we are making a list it's is eq? that data only. In R6RS you can refrain from exporting so that code that uses memoization doesn't really have access to mess with it. All lists that look like it eg ("get-proc") won't be eq? so it can be used as an argument without getting the original procedure.
Since you are not using a standard hash procedure from (rnrs hashtables) or SRFI-69 it's not possible for me to check it but since you are using a list as key your hashtable must use equal? as test. This is often a source of frustration when using hash tables in most lisps.

Scheme object is not applicable - 2 different ways

I am writing some code for a roster management program in scheme, but encounter an issue when I try to add a student. I am using a roster parameter which is a list of lists, each sublist being a student's record. The first student I add works without issue, however when I attempt to add a second student, I receive one of two instances of the same error.
If I try to enter a student who should be added, I get the error :
The object ("21" "Anon Ymous" "89") is not applicable.
If the student I add has information that conflicts with the existing student, I get the error :
The object (("23" "Anon Ymous" "11")) is not applicable.
The code for this section is as follows :
(define addifnotcontains
(lambda (roster item)
(cond ((null? roster) (list item))
((equal? (car (car roster)) (car item))
(begin
(display "\tID Already Exists\n")
(roster)
))
((equal? (cadr (car roster)) (cadr item))
(begin
(display "\tName Already Exists\n")
(roster)
))
(else (cons ((car roster) addifnotcontains (cdr roster))))
)
)
)
and the call for this function is (menu (addifnotcontains roster (buildobject 0 '()))) where menu is a lambda function that simply takes in a roster
I know that this issue has been caused by mismatched or misplaced parentheses in my code previously, but I cannot tell why it is occurring this time. I suspect it has something to do with calling (roster) at the end of the begin block, but as I understand it, the begin block returns the last value called within the block. What am I doing wrong?
In the first case, you're trying to apply (car roster) to addifnotcontains and (cdr roster):
(cons ((car roster) addifnotcontains (cdr roster)))
^ <-- This is one expression --> ^
You're also only passing one argument to cons and forgetting to pass along item.
This should be
(cons (car roster) (addifnotcontains (cdr roster) item))
In the second case, you're trying to apply roster as a function.
You're correct in that the value of a begin block is the value of the last expression in the block, but in your case this expression should be roster, not (roster).
(Remember that you can't add parentheses willy-nilly in Scheme like you can in some other languages; parentheses are always significant.)

Efficiency issue on Guile SCHEME: member and equal?

I am new on SCHEME programming and after some while I succeeded on writing a couple of scripts to deal with long maths formulas.
The issue is that its execution is pretty slow. After profiling I realized that the execution takes about 35% of the time executing the built-in functions equal? and member.
My question is regarding whether there exist more efficient version of those functions or I should re-factor the code in order to remove its calls?
I'm about to re-write the code so I would really appreciate any piece of advice.
Thanks!
EDIT 1:
I add some code for making the question clearer. The following function takes two parameters, a pair (x,y) and a term, x and y are usually variable names(symbols), but they may be a symbol and a number, a list and a symbol, etc. The term is a list such as
(+ w y z (* (- 1) x))
so after the function execution I would get something like
(+ w z)
The code looks like the following:
(define (simp-by-term_eq t_CC t)
(cond
((and (member (list '* (list '- 1) (car t_CC)) (cdr t))
(member (cadr t_CC) (cdr t)))
(delete-1st (cadr t_CC)
(delete-1st (list '* (list '- 1) (car t_CC)) (cdr t)))
)
((and (member (list '* (car t_CC) (list '- 1)) (cdr t))
(member (cadr t_CC) (cdr t)))
(delete-1st (cadr t_CC)
(delete-1st (list '* (car t_CC) (list '- 1)) (cdr t)))
)
(else t)
)
)
There are several conditions like the previous one on the function.
The equal function calls are mainly used into filter callse such as
(filter (lambda (x) (and (not (equal? (list '* (car t_CC) (list '- 1)) x))
(not (equal? (list '* (list '- 1) (car t_CC)) x)))) t)
equal? needs to test every part of list structure so basically it traverses both operands until it has touched all sequences and compared all atomic values. If you need to test if it's the same you can use eq? which only checks that the pair has the same address. Thus it will evaluate to #f even when the list structure look the same. Sometimes that's ok. eqv? is similar to eq? but it also works for numbers and characters (but not strings).
You can use memq that uses eq? instead of equal? and you can use memqv that uses eqv? which also works for numbers and characters.
I'm not that familiar with Guile but if you have performance issues and are using member perhaps you should consider hash tables instead?
It could happen you need to rethink the algorithm you use too if you really need to use equal?, but without specific code it's difficult to be more specific.

How Do For Loops Work In Scheme?

I'm having some difficulty understanding how for loops work in scheme. In particular this code runs but I don't know why
(define (bubblesort alist)
;; this is straightforward
(define (swap-pass alist)
(if (eq? (length alist) 1)
alist
(let ((fst (car alist)) (scnd (cadr alist)) (rest (cddr alist)))
(if (> fst scnd)
(cons scnd (swap-pass (cons fst rest)))
(cons fst (swap-pass (cons scnd rest)))))))
; this is mysterious--what does the 'for' in the next line do?
(let for ((times (length alist))
(val alist))
(if (> times 1)
(for (- times 1) (swap-pass val))
(swap-pass val))))
I can't figure out what the (let for (( is supposed to do here, and the for expression in the second to last line is also a bit off putting--I've had the interpreter complain that for only takes a single argument, but here it appears to take two.
Any thoughts on what's going on here?
That's not a for loop, that's a named let. What it does is create a function called for, then call that; the "looping" behavior is caused by recursion in the function. Calling the function loop is more idiomatic, btw. E.g.
(let loop ((times 10))
(if (= times 0)
(display "stopped")
(begin (display "still looping...")
(loop (- times 1)))))
gets expanded to something like
(letrec ((loop (lambda (times)
(if (= times 0)
(display "stopped")
(begin (display "still looping...")
(loop (- times 1)))))))
(loop 10))
This isn't actually using a for language feature but just using a variation of let that allows you to easily write recursive functions. See this documentation on let (it's the second form on there).
What's going on is that this let form binds the name it's passed (in this case for) to a procedure with the given argument list (times and val) and calls it with the initial values. Uses of the bound name in the body are recursive calls.
Bottom line: the for isn't significant here. It's just a name. You could rename it to foo and it would still work. Racket does have actual for loops that you can read about here.

Resources