change the set method to put
Misty Programming Language:

Objects

Misty is an object-oriented language but it is not a classical language: Objects are not rigidly defined by classes. Instead, Misty's objects are soft and malleable. Misty unifies objects and associative data structures. Objects are unordered containers of key/value pairs. Misty objects can have members added or removed at any time. Objects are passed by reference. Objects are mutable, but references can be made immutable by the fix prefix operator. Members can be accessed using either a dot notation or a bracket notation.

Making a new object

There are two ways to make a new object: object literals or the object operator.

Object Literal

A new object can be made with an object literal.

object_literal => '{' pair, '}'

pair
=> name [':' expression | parameter_list function_body]
=> text [':' expression]

In the object literal notation, the specification of an object begins with { (left brace) and ends with } (right brace). Between them are zero or more name/value pairs, separated by , (comma). A name/value pair is an identifier or text, followed by : (colon) followed by an expression. (See JSON.) Each pair contributes a member to the object.

If the value is a function, then the abbreviated method form can be used. See Functions.

If no value is supplied, then the value of the member is assumed to be true. This is useful when using objects as sets.

An empty object can be made by {} (braces).

empty_object : {}    # empty_object is {}

color : {            # color is {
    aliceblue,       #     "aliceblue"    : true,
    antiquewhite,    #     "antiquewhite" : true,
    aqua             #     "aqua"         : true
}                    # }

The statement:

stooge : {first : "Curly", last : "Howard"}

has the same result as

stooge : {}
stooge.first : "Curly"
stooge.last : "Howard"

object Operator

The object prefix operator makes a new mutable object that copies all of the members from an old mutable object.

object null produces a new, empty mutable object. It is the same as {}.

Members

The members of an object can be accessed with either the dot notation or the subscript notation.

Dot notation

The dot notation is usually the most convenient notation for accessing the members of an object. It takes an object and an identifier.

Example:

stooge : {first : "Curly", last : "Howard"}
first_name : stooge.first      # first_name is "Curly"
first_name : null.first        # first_name is null
new_stooge : object stooge
new_stooge.first : "Shemp"     # new_stooge is {first : "Shemp"}
last_name : new_stooge.last    # last_name is "Howard"

Subscript notation

The subscript notation is similar to the dot notation, but instead of taking an identifier (which is used as a text) it can take an expression of any type except null. It can be used for dynamically making member names, or for creating keys which are not texts. Type is significant in keys. The key 0 (a number) is distinct from the key '0' (a text). A key 'a' (lower case) is distinct from a key 'A' (upper case). The subscript expression is wrapped in [ (left bracket) and ] (right bracket).

The subscript notation is used with objects, arrays, and texts. Using it with any other type raises a 'type' exception.

Example:

stooge : {first : "Curly", last : "Howard"}
first_name : stooge['first']    # first_name is "Curly"
new_stooge : object stooge
new_stooge['first'] : "Shemp"
    # new_stooge is {first : "Shemp"}
new_stooge['aka'] : "Samuel Horwitz"
    # new_stooge is {aka : "Samuel Horwitz", first : "Shemp"}

Operators

See Operators to see more about these operators.

can

The can operator returns true if its first operand is an object and if its first operand has a member that is a function that is named by the second operand.

fix

The fix prefix operator returns an immutable reference. It is not possible to directly modify an object with a fixed reference, or to extract its methods or functions. A fixed reference allows for read only access to the data members and invocation of its methods and functions.

has

The has operator returns true if its first operand is an object and if its first operand has a member that is named by the second operand.

is data

The is data operator returns true for objects.

is fix

The is fix operator returns true if its operand is immutable.

is object

The is object operator returns true for objects.

object

Make a new object that inherits from the operand.

Sets

Objects can be used as sets. A set is an unordered collection of names. If we ignore the values in an object, the keys work just like a set. By convention, we use true as the value of members of a set but you can use any values you like except null. An array can be converted into a set object with array.object(true). The object.keys method can be used to turn a set object into an array.

The methods union, intersection, and difference create a new set from two existing sets. One of the sets is always an object. The other set can be an object, or an array that will be converted into a set object for you, or null which will be interpreted as the empty set.

Foreign Objects

A Misty system may be working in an environment that provides objects that work very differently than Misty's objects. Such foreign objects can be wrapped to act as Misty objects. Such objects will have three methods: get, set, and call.

Methods

The method invocation pattern is

object.method_name(parameter_list)

The object is searched for a member matching the method_name. The search skips values which are not functions. If the result of that search is not a function, then the list of built-in function methods will be searched. This avoids conflicts in the use of data members that happen to have the same names as the methods. It is possible, for example, to use text as both a data member and a method.

define my_object : {text : "Carl Hollywood"}
result : my_object.text()
    # result is '{text: "Carl Hollywood"}'

These are the built-in methods. They are available to all objects except foreign objects. All of these methods may be overridden.

object.array(dimension, init)

Numbers can be used as keys, so objects can be used as sparse arrays. The array method can turn a sparse object into a normal array. It does this by first creating an array of a given size, with all of the elements initialized to the specified init value. Then every member in the object with an integer key that fits within the dimension is copied into the array.

It returns a new array.

object.call(method, arguments)

The call method is an alternate way of invocating a method. The method parameter is a text containing the name of one of the object's methods. Otherwise a 'type' exception will be raised. If the object does not have a function value associated with the method name, then a 'function' exception will be raised.

If arguments is null, then the method will be invoked with no arguments. If arguments is an array, then its values will be passed to the method as arguments. Otherwise a 'type' exception will be raised.

It returns the result of calling the method on the object.

object.combine(second)

The combine method takes all of the members of a second object and copies them in this object. If the second object contain members sharing the same keys, the new values replace the old ones in this object. If the first object is fixed, then a 'fix' exception will be raised. Also see the {} augmentation suffix operator and the object.union method.

It returns the expanded, modified object.

object.difference(second)

The difference method treats the object as a set, producing the difference of the object and a second object or array.

It returns a new object containing all of the members of the object that do not have the same keys as members of second.

object.equal(second)

The equal method returns true if the object and a second object are equal. This is a shallow test of equality: the objects must both contain the same own enumerable key/value pairs. Pairs with keys that are not strings are ignored.

It could be implemented like this:

def equal(first, second) {
    def first_keys : first.keys()
    def second_keys : second.keys()
    var key
    if first_keys <> second_keys then
        return false
    fi
    for key in first_keys do
        if not (first[key] eq second[key]) then
            return false
        fi
    od
    return true
exception e 
    return false
}

Examples:

a : {first: "Carl", last: "Hollywood"}
b : {first: "Carl", last: "Hollywood"}
result : a eq b        # result is false
result : a = b         # result is true
result : a.equal(b)    # result is true

a.next : a
b.next : a
result : a.equal(b)    # result is true

a.next : b
result : a.equal(b)    # result is false

object.finalize(method)

The finalize method registers a finalization method with the object. Multiple finalization methods can be added to any object.

When the object is ready to be garbage collected, the finalization methods will be invoked. If a finalization method causes the program to become aware of the object, then the object will not be garbage collected, but all of its finalization methods will be removed.

It returns the object.

object.get(name)

The get method returns a value from an object. It can be implemented as

method get(name) {
    return $[name]
} 

It returns a value.

object.intersection(second)

The intersection method treats this object as a set, producing the intersection of this object and a second object or array.

It returns a new object containing the members of this object that have the same keys as members of the second.

object.keys()

The keys method makes an array containing all of the own keys in the object that are texts. The keys are not guaranteed to be in any particular order. If the object is fixed then it raises a 'fix' exception.

It returns an array of keys.

object.pretty()

The pretty method makes a text from the object, adding line breaks and indentations to make it more readable.

It returns a text.

object.remove(key)

The remove method removes the own member associated with the key from the object. If there is no member with a matching key, then nothing is removed. If key is an array, then it is treated as an array of keys that will all be removed from the object.

Changing a member's value to null will also remove it. Also see the object.difference method.

It returns the modified object.

object.set(name, value)

The set method stores a value in an object. It could be implemented as

method set(name, value) {
    if $ is fix then
        raise 'fix'
    fi
    $[name] : value
    return $
} 

It returns the object. It raises a 'fix' exception if the object is fixed.

object.text()

The text method makes an Extended JSON text from the object.

It returns a text.

object.union(second)

The union method treats the object as a set, producing the union of this object and a second object or array.

It returns a new object that contains all of the members of the object and the second object. Where the objects' keys intersect, this object's members will be used.

object.values(keys)

The values method returns an array containing the values of members of the object. If the object is fixed then it raises a 'fix' exception. If keys is null, then only own values having keys that are texts are collected. The values are not guaranteed to be in any particular order; it might not be the same order as obtained from the object.keys() method. If keys is an array of keys (usually obtained from the object.keys method), then the keys are used to determine the order of the values.

It returns an array of values.

Example:

o : {first: "Carl", last: "Hollywood"}
result : o.values()                     # result is ["Hollywood", "Carl"]
result : o.values(['first', 'last'])    # result is ["Carl", "Hollywood"]

Operator Methods

Binary operator Method
+ add
- subtract
* multiply
/ divide
÷ idivide
** raise
& concat
&&& concats
= equal
< lt
> gt
<= le
>= ge
apply apply
mod mod
min min
max max
Unary operator Method
+ number
- negate
& text
abs abs
arity arity
int int
name name
not not
parameters parameters

Many of the unary and binary operators can be used on objects that contain suitable methods. For example, if myobject contains an add method, then

myobject + value

has the same result as

myobject.add(value)

Similarly,

-myobject

has the same result as

myobject.negate()

and

a <> b

has the same result as

not (a.equal(b))

This makes it possible to create specialized number types which use objects to implement special characteristics (such as bignum or complex) and to use those objects with the normal operators. Note that only two of these methods (text and equal) are implemented by default for objects.

Note that eq is always concerned with exactly the same object. It can be overridden. The = operator used on an object will call the equal method, which can be overridden.

$ will be bound to the first operand. Unary methods must have an arity of 0. Binary methods must have an arity of 1.

More on keys

Generally, texts are used as keys, but any value can be used as a key. Only texts can be used in object literals and with the . operator, but any value may be used with the [ ] operator. Only text keys are enumerated by the object.keys method.

An object key can be used to add a secret property to an object. The property value can only be obtained by presenting both objects.

An object key does not act as a memory root. If all references to a key are lost, then the property using the lost object as a key is deleted.

The Works

Get

The Misty machine uses a get process to retrieve values from an object. If the object is fixed, then the value obtained will also be fixed. Also, if the object is fixed, function values will be treated as null values.

If there is no key in the object that exactly matches the key specified in the dot notation or subscript notation (as determined by the be operator), then the result is null, not an error or exception. This makes it easy to query an object.

Call

The Misty machine uses a call process to retrieve function values from an object. The call process is similar to the get process with a couple of differences.

The call process is only interested in function values.

If the function is not found, it will look to the built-in methods.

The is function operator and the can operator use the call process rather than the get process.

Put

The Misty machine uses a put process to store values into an object. If the object is fixed, then a 'fix' exception is raised unless the object being modified is $ and if the current method was called with the method invocation pattern and not by the call or apply methods. If the key text already exists in the object, then the value is replaced. If the key text does not already exist in the object, the new key and value are added. If the value being stored is null, then the member is deleted.