In this Section we shall implement a dictionary object that works on Numbers, Characters and Symbols7.1.
These procedures are probably not sufficient, but they give a nice overview of the possibilities.
(define d (make-dict)) ==> unspecified (dict-key? d 4) ==> #f (dict-ref d 4) ==> error (dict-set! d 4 (list 'a 'b)) ==> unspecified (dict-set! d "x" "y") ==> error (dict-key? d 4) ==> #t (dict-ref d 4) ==> (a b) (set-car! (dict-ref d 4) 'b) ==> unspecified (dict-set! d #\H "hello") ==> unspecified (dict->list d) ==> ((4 b b)) (#\H . "hello"))
The first functions, the equivalents of make-dict, dict-ref and dict-key? are pretty straightforward.
def makeDict(): return {} def dictRef(d, key): if not isinstance(d, dict): schemefct.error("Not a dictionary", d) return d[key] def isDictKey(d, key): if not isinstance(d, dict): schemefct.error("Not a dictionary", d) return schemefct.schemeBool(d.has_key(key))
Some remarks are in order. First of all, notice how we use the Psyche function error to raise explicit errors; on the other hand, for the dict-ref procedure we rely on Python's behavior of raising a KeyError when a key is not present.
Furthermore, the names of the Python procedures are created from the Scheme names by using the name mangling scheme from Chapter 6.
Finally, notice how we have to convert Python boolean values to Scheme boolean values using the schemeBool function. This is very important, since Scheme booleans have different semantics from Python booleans.
The dict-set! procedure is a bit more interesting. It will use the isNumber, isChar and isSymbol functions from schemefct to check the key.
def dictSet(d, key, value): if not isinstance(d, dict): schemefct.error("Not a dictionary", d) if not (schemefct.isNumber(key) or schemefct.isChar(key) or schemefct.isSymbol(key)): schemefct.error("Invalid key", key) d[key] = value
Notice how this function has no return value; this is the preferred behavior when implementing Scheme procedures with undefined return values.
The last one, dict->list, is the most complicated. In this example, it uses the schemefct.list_ and schemefct.cons methods; it would also have been correct to import the Pair type from psyche.types and use them directly.
def dictToList(d): if not isinstance(d, dict): schemefct.error("Not a dictionary", d) # assoc is a python list of pairs assoc = [schemefct.cons(key, value) for (key, value) in d.items()] # schemefct.list_ requires a list of arguments return schemefct.list_(*assoc)
There are several ways of creating new environments. This Section will show how it is done in Psyche.
First of all, we add one more statement to the psychedict module we have created in the previous chapter:
procedures = {"make-dict": makeDict, "dict-key?": isDictKey, "dict-set!": dictSet, "dict-ref": dictRef, "dict->list": dictToList}
With this statement, we map Scheme procedure names to Python function objects.
Now we go to the code where we actually want to instantiate a new interpreter using these functions. We start out by creating a new Scheme environment and we update it with our new procedures.
from psyche import interpreter import psychedict # code... env = interpreter.SchemeEnvironment5() env.update(psychedict.procedures)
That's it! With these two lines of code we have registered the new dictionary procedures with the Scheme environment.
Instantiating the new interpreter then becomes trivial.
# code... # code creating the new environment env i = interpreter.Interpreter(env) i.eval("(define d (make-dict))") i.eval("(dict-set! d 4 4)") print i.eval("d") # this will print {4: 4}