272 lines
8.2 KiB
Racket
272 lines
8.2 KiB
Racket
#lang scribble/manual
|
|
@(require (for-label racket))
|
|
|
|
@title{IdentityDictionary}
|
|
associative collection mapping keys to values@section{related}
|
|
Classes/Environment, Classes/Event
|
|
@section{categories}
|
|
Collections>Unordered
|
|
|
|
@section{description}
|
|
|
|
An IdentityDictionary is an associative collection mapping keys to values. Keys and values can be arbitrary objects, but for keys, often a link::Classes/Symbol:: is used.
|
|
|
|
Often, the subclass link::Classes/Event:: is used as an IdentityDictionary, because there is a syntactical shortcut:
|
|
|
|
@racketblock[
|
|
a = (); // return a new Event.
|
|
a.put(\foo, 2.718);
|
|
a.at(\foo);
|
|
::
|
|
|
|
]
|
|
@section{Note}
|
|
|
|
Keys match only if they are strong::identical objects::. (i.e. === returns true. In Dictionary, keys match if they are equal valued. This makes IdentityDictionary faster than link::Classes/Dictionary::)
|
|
|
|
The contents of a Dictionary are strong::unordered::. You must not depend on the order of items in a Dictionary.
|
|
::
|
|
|
|
|
|
IdentityDictionary is often used to assign names to instances of a particular class. For example, the proxy classes ( link::Classes/Pdef::, link::Classes/Pdefn::, link::Classes/Tdef::, link::Classes/Ndef:: ), and link::Classes/NodeWatcher:: all have class variables named all implemented as IdentityDictionaries.
|
|
|
|
@section{Subsection}
|
|
'parent' and 'proto' variables
|
|
|
|
IdentityDictionary has three levels of content: the dictionary itself, a
|
|
@racketblock[proto::, and a ]
|
|
|
|
@racketblock[parent::. ]
|
|
|
|
@racketblock[proto:: and ]
|
|
|
|
@racketblock[parent:: exist primarily for IdentityDictionary's subclass link::Classes/Event::, which uses the ]
|
|
|
|
@racketblock[parent:: to store action functions that will be used when an event is played. Users may put additional default values into the ]
|
|
|
|
@racketblock[proto::.
|
|
|
|
Looking up a key within a dictionary first checks the dictionary itself. If the key is not found at this level, it looks in the ]
|
|
|
|
@racketblock[proto::, and if still not found, it looks in the parent.
|
|
|
|
These extra levels are meant for common, default values that should be the same across many dictionary instances.
|
|
|
|
]
|
|
@section{list}
|
|
|
|
##
|
|
@racketblock[proto:: and ]
|
|
|
|
@racketblock[parent:: values are not shown when posting the dictionary.
|
|
## Copying an IdentityDictionary transfers a reference to the parent and proto into the copy. It is a reference, not a copy: changing the copy's parent also affects the parent of the original. This is for efficiency.
|
|
## Iteration operations (link::Classes/Dictionary#-do::, link::Classes/Dictionary#-keysValuesDo::) only touch the main-level values.
|
|
## link::Classes/Dictionary#-collect::, link::Classes/Dictionary#-select::, and link::Classes/Dictionary#-reject:: return new dictionaries. The parent and proto will be preserved emphasis::as is:: in the new result, but -- as with ]
|
|
|
|
@racketblock[do:: -- they will not be processed in the iteration, and as with ]
|
|
|
|
@racketblock[copy::, they are identical references.
|
|
::
|
|
|
|
]
|
|
|
|
@racketblock[
|
|
// 'b' is not posted
|
|
e = IdentityDictionary(8,
|
|
parent: IdentityDictionary[\b -> 10]
|
|
).put(\a, 5);
|
|
-> IdentityDictionary[ (a -> 5) ]
|
|
|
|
// But 'b' is still there
|
|
e[\a] + e[\b]
|
|
-> 15
|
|
|
|
// Iteration touches 'a' only
|
|
e.keysValuesDo { |key, value| [key, value].postln };
|
|
-> [ a, 5 ]
|
|
|
|
// 'collect' is also an iteration, and doesn't touch the parent
|
|
g = e.collect { |x| x * 5 };
|
|
-> IdentityDictionary[ (a -> 25) ]
|
|
g[\b]
|
|
-> 10
|
|
|
|
// The parent goes into a copy as well
|
|
g = e.copy;
|
|
g[\b]
|
|
|
|
// But it's by reference: 'e' and 'g' have the same parent
|
|
g.parent[\b] = 20;
|
|
e[\b]
|
|
-> 20
|
|
::
|
|
|
|
|
|
]
|
|
@section{CLASSMETHODS}
|
|
|
|
|
|
@section{method}
|
|
new
|
|
The link::#-parent:: and link::#-proto:: instance variables allow additional IdentityDictionary's to provide default values. The precedence order for determining the value of a key is the IdentityDictionary, its prototype, its parent.
|
|
|
|
When the instance variable link::#-know:: is link::Classes/True::, the IdentityDictionary responds to unknown messages by looking up the selector and evaluating the result with the dictionary as an argument. For example:
|
|
|
|
@racketblock[
|
|
a = IdentityDictionary(know: true);
|
|
a.put(\foo, { | x, y | "--".postln; ("x:" ++ x).postln; ("y:" ++ y).postln; y.squared });
|
|
a.foo(-10.01);
|
|
::
|
|
|
|
|
|
]
|
|
@section{warning}
|
|
|
|
Only keys that are not already instance methods of IdentityDictionary (or its superclasses) can be used in such a way. E.g. the key "free" will not work, because it is implemented in Object. This means that class extensions (see: link::Guides/WritingClasses::) can break code. It is advisable to use underscores in method names to make this improbable.
|
|
::
|
|
|
|
In the subclass link::Classes/Event::, "know" is true by default, so that it can be instantly used for prototype objects. The first argument passed to the functions is in such cases always the dictionary/event itself (here denoted by "self").
|
|
|
|
|
|
@racketblock[
|
|
a = (some_value: 7, fuzzy_plus: { |self, a, b| a + b * rrand(0.9, 1.1) });
|
|
a.some_value; // returns 7
|
|
a.some_value = 8; // sets it to 8
|
|
a.fuzzy_plus(7, 4);
|
|
::
|
|
|
|
]
|
|
@section{INSTANCEMETHODS}
|
|
|
|
|
|
|
|
@section{method}
|
|
know
|
|
If set to true, the dictionary interprets method calls as look ups. This allows you to implement object prototypes (see above).
|
|
|
|
@section{method}
|
|
putGet
|
|
Sets key to newValue, returns the previous value of key.
|
|
|
|
@racketblock[
|
|
a = (z: 100);
|
|
x = a.putGet(\z, -1); // x is now 100
|
|
::
|
|
|
|
]
|
|
@section{method}
|
|
includesKey
|
|
Returns true if the key exists in the dictionary
|
|
|
|
@racketblock[
|
|
(j:8).includesKey(\j) // true
|
|
::
|
|
|
|
]
|
|
@section{method}
|
|
findKeyForValue
|
|
Returns the key for a given value (it'll return the first it finds, so this may be ambiguous).
|
|
|
|
@racketblock[
|
|
(j:8, k: 9).findKeyForValue(8); // returns \j
|
|
::
|
|
If such reverse lookup is needed a lot, for efficiency you may consider using a link::Classes/TwoWayIdentityDictionary:: instead.
|
|
|
|
|
|
]
|
|
@section{method}
|
|
proto, parent
|
|
|
|
The two instance variables proto and parent may hold dictionaries which are used to look up all those keys that have no value in the current dictionary.
|
|
First, proto is looked up, then parent. In other words: proto overrides parent. This allows you to construct systems with complex defaults or multiple inheritance.
|
|
|
|
|
|
@racketblock[
|
|
x = (freq: 30);
|
|
a = (amp: 1).parent_(x);
|
|
a.at(\freq); // returns 30
|
|
a.proto_((freq: 20));
|
|
a.at(\freq); // returns 20
|
|
y = (i: -1);
|
|
b.parent_(y);
|
|
a.at(\i); // returns -1
|
|
a.cs;
|
|
|
|
::
|
|
|
|
]
|
|
@section{image}
|
|
IdentityDictionary_02.png#Setting the parent of a dictionary.::
|
|
|
|
|
|
@racketblock[
|
|
x = (freq: 30);
|
|
a = (amp: 1).parent_(x);
|
|
y = (freq: 300);
|
|
b = (amp: 0.5).parent_(y);
|
|
a.parent_(b);
|
|
a.at(\freq); // returns 300
|
|
a.cs;
|
|
|
|
::
|
|
|
|
]
|
|
@section{image}
|
|
IdentityDictionary_01.png#Example schema: order of overriding in proto and parent.::
|
|
|
|
|
|
@section{method}
|
|
insertParent
|
|
Inserts a dictionary into the chain of parents of the receiver (rather than replacing the parent).
|
|
@section{argument}
|
|
newParent
|
|
The dictionary that is added to the parent chain
|
|
@section{argument}
|
|
insertionDepth
|
|
Level at which the new parent is inserted. Zero (default) means directly above, Inf means at the top of the parent chain.
|
|
@section{argument}
|
|
reverseInsertionDepth
|
|
If the new parent dictionary has parents itself, this parameter specifies where the original parents are placed in the new parent chain. Zero means directly above, Inf (default) means at the top of the chain.
|
|
|
|
|
|
@section{image}
|
|
IdentityDictionary_03.png#Compare a.insertParent(b, 0) and a.insertParent(b, 1)::
|
|
|
|
@section{image}
|
|
IdentityDictionary_04.png#Compare a.insertParent(b, 0, inf) and a.insertParent(b, 0, 0)::
|
|
|
|
@section{subsection}
|
|
Timing support (Quant)
|
|
|
|
@section{method}
|
|
nextTimeOnGrid, asQuant, timingOffset
|
|
|
|
Use a dictionary to represent timing information.
|
|
|
|
|
|
@racketblock[
|
|
(
|
|
SynthDef(\help_sinegrain,
|
|
{ arg out=0, freq=440, sustain=0.05;
|
|
var env;
|
|
env = EnvGen.kr(Env.perc(0.01, sustain, 0.2), doneAction: Done.freeSelf);
|
|
Out.ar(out, SinOsc.ar(freq, 0.5pi, env))
|
|
}).add;
|
|
|
|
a = Pbind(\instrument, \help_sinegrain, \note, Pseq([0, 7, 2, 9, 11, 10, 9, 8], inf), \dur, 1);
|
|
a.play(quant:(quant: 1, phase: 0));
|
|
a.play(quant:(quant: 1, phase: 1/3));
|
|
a.play(quant:(quant: 1, phase: 1.0.rand));
|
|
)
|
|
::
|
|
|
|
]
|
|
@section{subsection}
|
|
Garbage collection
|
|
|
|
@section{method}
|
|
freezeAsParent
|
|
For optimizing the garbage collector load, objects can be frozen and become immutable. This method creates a new dictionary with the frozen instance as a parent so that all contents can be overwritten without losing this optimization.
|
|
|
|
|
|
|