If you’re familiar with JavaScript objects, then much of what I’m going to write will be familiar to you. These objects are different from what we’re used to in Smallworld GIS Magik. Like many OO languages, Magik uses class-based (or classical) inheritance while JavaScript uses prototypal inheritance.
The difference is that instead of defining the structure of an object via a class template (the exemplar in Magik), prototypal objects simply inherit from other objects.
We can also dynamically add new values and methods after an object has been created, which all its children immediately inherit. There’s no need to recompile code or follow a strict hierarchy. If we want to change an object’s inheritance tree at runtime, we simply replace the existing parent object (i.e. its prototype) with a new one and that object now inherits from the new one.
Try doing that with class-based inheritance.
Of course class-based inheritance is not bad. Far from it. There are definite use cases and I often rely on it. However there are many instances where it’s simply not the right tool for the job, but since we, as Magik programmers, usually only know about exemplars, we try to shoehorn all problems into the classical-inheritance shoebox.
But let’s step back a moment and look at what a JavaScript object is. In reality it’s simply a container of values and methods. Each value or method has a name and we access them through this name.
Look at the following code.
const starship = {
name:"Heart of Gold",
colour:"white",
propulsion:"Inifinite Improbability Drive",
owner: function(name) {
return "The owner is " + name
}
};
See how the object is simply a set of name/(value/method) pairs? Very similar to a property_list in Magik. In JavaScript these pairs are called properties and you access them in much the same way you access regular Magik methods – that is, using dot notation with the period.
So if we type starship.name, it will return Heart of Gold. If we type starship.owner(“Zaphod”), we invoke a method and get back, The owner is Zaphod.
There is other functionality built into JavaScript objects, but for our purposes, these are the important ones.
The beeble_object
With that prelude out of the way, let me introduce the beeble_object – based on JavaScript’s prototypal object.
I implemented it many years ago because I wanted a flexible, lightweight object that could be quickly created and assigned properties. Since that time I’ve discovered it’s very useful in numerous situations and now I use it everywhere.
Here are some examples.
Reading JSON from Disk
Let’s say we have the following JSON configuration file stored on disk and want to read it into a program.

We simply do this…
Magik> package_json << beeble_object.new()
a beeble_object
Magik>
package_json.from_json_file("D:\Beeble\frontend\package.json")
a beeble_object
Magik>
Magik> package_json.name
"material-dashboard-pro-react"
Magik>
Magik> package_json.version
"1.0.0"
Magik>
Line 5 reads the JSON data from the file and then we can access properties using standard dot notation.
Writing JSON to Disk
We can also do it the other way around by instantiating a beeble_object and then creating properties on it.
Magik> config << beeble_object.new()
a beeble_object
Magik>
Magik> config.version << "1.0"
"1.0"
Magik> config.default_name << "config.txt"
"config.txt"
Magik> config.action << "open"
"open"
Magik> config.files << {"css_loader", "react", "moment", "material_ui"}
sw:simple_vector:[1-4]
Magik>
Magik> config.save_json_file("D:\Beeble\config.txt")
True
Magik>
In lines 5, 8, 11 and 14 we create properties on the object and then, in line 18, we save the object to a file in JSON format. If we look at the output in D:\Beeble\config.txt, we see…
{"action":"open",
"default_name":"config.txt",
"files":["css_loader","react","moment","material_ui"],
"version":"1.0"}
Since the file is standard JSON, it can be used by the plethora of applications that understand JSON, thereby allowing Smallworld GIS data to be exported to other systems.
Implementing the Module Pattern
And here’s an example of beeble_object being used in the module pattern to export public procedures via a hard object (i.e. an object that only contains methods, no data).
_block
_global test_module
test_module <<
_proc @test_module()
_local l_first_name << "Zaphod"
_local l_last_name << "Beeblebrox"
_local l_ship << "The Heart of Gold"
_constant WRITE_PARAM << _proc @write_param(p_str)
write(p_str)
_endproc
_constant EXPORT << beeble_object.new()
EXPORT.first_name << _proc @first_name()
_import l_first_name
_import WRITE_PARAM
WRITE_PARAM(l_first_name)
_endproc
EXPORT.last_name << _proc @last_name()
_import l_last_name
_import WRITE_PARAM
WRITE_PARAM(l_last_name)
_endproc
EXPORT.ship << _proc @ship()
_import l_ship
_import WRITE_PARAM
WRITE_PARAM(l_ship)
_endproc
_return EXPORT
_endproc()
_endblock
Lines 6 to 8 create private variables and line 10 creates a private method, all of which are not accessible outside the module. Line 15 creates a new beeble_object (named EXPORT) to which we add properties we want to define as public.
Then in line 38 we return this object so the properties it contains are accessible outside the module. Note we immediately execute test_module() by appending the parentheses at the end in line 40.
When we want to use the ship() method, it can be invoked by typing test_module.ship() at the Magik prompt. Although ship() uses the private variable l_ship and the private procedure WRITE_PARAM, notice neither of these are available from outside the module because they are held in closures — they are truly private.
What you’ve just seen is a standard constructor pattern that returns an object implementing the module pattern.
Passing Arguments via Soft Objects
Beeble objects can also be used to collect multiple arguments so we can pass one argument to a method or procedure via a soft object (i.e. an object that only contains data, no methods) rather than having to pass multiple individual arguments.
Magik> args << beeble_object.new({:a, 2, :b, 5, :c, 100})
a beeble_object
Magik>
Magik> result << calculate_product(calculate_sum(args))
This is a good pattern to follow if we want to nest procedure calls as we’ve done in line 5 (note, in line 1, we can create a new beeble_object with predefined properties by passing its new() constructor a simple_vector containing key/value pairs).
We can also populate a new beeble_object with properties by passing key/value pairs directly to the new() constructor as shown below in line 1.
Magik> args << beeble_object.new(:a, 2, :b, 5, :c, 100)
a beeble_object
Magik>
Magik> result << calculate_product(calculate_sum(args))
When nesting, each procedure must return the number of arguments required by the next outer one in the nested structure. However if we want the flexibility of being able to change the order of how we nest procs, we need to standardize on the number of arguments, in this case one. By passing an object up the nested hierarchy, we can technically pass one argument, but practically pass as many as we desire — all contained within the one object.
This pattern is also useful to ensure we don’t accidentally make mistakes with the order of our arguments.
If we want to ensure order doesn’t matter when passing arguments, we can pass in a beeble_object with parameter name/value pairs. So passing in…
Magik> args << beeble_object.new({:b, 5, :c, 100, :a, 2})
a beeble_object
Magik> result << calculate_product(calculate_sum(args))
…won’t break a procedure when a, b and c are out of order, because its parameters don’t depend on positional arguments, and each argument is specifically labelled.
as_json() and as_json_string()
The beeble_object also has a number of convenience methods defined on it. An important one is as_json() that converts a beeble_object to JSON and writes it to a stream. This is used by the print_on() method to allow the print procedure to display beeble objects in JSON format.
There’s also as_json_string() that converts a beeble_object to a JSON string.
Magik> bo << beeble_object.new()
a beeble_object
Magik> bo.name << "Zaphod"
"Zaphod"
Magik> bo.friends << {"Trillian", "Ford", "Arthur"}
sw:simple_vector:[1-3]
Magik> bo.ship << "Heart of Gold"
"Heart of Gold"
Magik> bo.acquaintances << beeble_object.new()
a beeble_object
Magik> bo.acquaintances.robot << "Marvin"
"Marvin"
Magik> bo.acquaintances.planet_builder << "Slartibartfast"
"Slartibartfast"
Magik> str << bo.as_json_string()
"{"acquaintances":{"planet_builder":"Slartibartfast","robot":"Marvin"}<newline>,"friends":["Trillian","Ford","Arthur"]<newline>,"ship":"Heart of Gold","name":"Zaphod"}<newline>"
Magik> write(str)
{"acquaintances":{"planet_builder":"Slartibartfast","robot":"Marvin"}
,"friends":["Trillian","Ford","Arthur"]
,"ship":"Heart of Gold","name":"Zaphod"}
Magik>
from_json()
The method from_json() takes a JSON string and turns it into a beeble_object.
Magik> json_str << '{"name":"Zaphod","gender":"Male","race":"Betelgeusian", "title":"President"}'
Magik> zaphod << beeble_object.new()
a beeble_object
Magik> print(zaphod)
{}
Magik> zaphod.from_json(json_str)
a beeble_object
Magik> print(zaphod)
{"gender":"Male","title":"President","race":"Betelgeusian","name":"Zaphod"}
Magik> zaphod.name
"Zaphod"
Magik> zaphod.race
"Betelgeusian"
Magik>
And of course, as I mentioned earlier, there’s the prototype.
The Prototype
Unlike classical inheritance (that Magik uses), prototypal inheritance allows objects to dynamically inherit from other objects. So inheritance isn’t locked down as it is in Magik.
For example, if we create an object (proto) and define properties on it, we can set it as the prototype of another object (e.g. child) which will inherit all its properties, as in line 28 below.
Magik> proto << beeble_object.new()
a beeble_object
Magik> proto.slarti << _proc @slarti()
write("I'm slarti from proto")
_endproc
proc slarti
Magik>
Magik> proto.trillian << _proc @trillian()
write("I'm trillian from proto")
_endproc
proc trillian
Magik>
Magik> proto.slarti()
I'm slarti from proto
Magik> proto.trillian()
I'm trillian from proto
Magik> child << beeble_object.new()
a beeble_object
Magik> child.slarti()
**** Error: Object a beeble_object does not understand message slarti()
Magik> child.prototype << proto
a beeble_object
Magik> child.slarti()
I'm slarti from proto
Magik> child.trillian()
I'm trillian from proto
Magik> child.slarti << _proc @slarti()
write("I'm slarti from child")
_endproc
proc slarti
Magik> child.slarti()
I'm slarti from child
Magik> child.trillian()
I'm trillian from proto
Magik> child.slarti << _unset
Magik> child.slarti()
I'm slarti from proto
Magik>
So although child does not have slarti() or trillian() methods (as we can see in line 26), we can still access them (lines 31 to 35) because its prototype is set to the proto object.
If we now dynamically define a slarti() method on child, that will override the prototype’s slarti() method as we would expect (lines 37 to 43). The difference is we can dynamically override superclass properties at runtime and remove those overridden methods (lines 48 to 51).
Dynamically Changing Superclasses
But that’s not all. We can also dynamically change superclasses at runtime.
Magik> new_proto << beeble_object.new()
a beeble_object
Magik> new_proto.marvin << _proc @marvin()
write("I'm marvin from new_proto")
_endproc
proc marvin
Magik> new_proto.marvin()
I'm marvin from new_proto
Magik> child.marvin()
**** Error: Object a beeble_object does not understand message marvin()
Magik> child.prototype << new_proto
a beeble_object
Magik> child.marvin()
I'm marvin from new_proto
Magik> child.slarti()
**** Error: Object a beeble_object does not understand message slarti()
Magik>
Notice how we changed child‘s superclass from proto to new_proto by simply setting the new prototype in line 15. Now child inherits properties from new_proto rather than proto as shown in lines 18 and 19. Lines 21 and 22 show child no longer inherits from proto.
Working with Properties
Properties are the fundamental building blocks of a beeble_object. A property consists of a key (or name) and a value. The value can be any Magik object. If the value is a procedure, then we say the property is a method.
This makes a beeble_object similar to a number of other built-in Magik objects, such as the concurrent_hash_map, property_list and hash_table. However since the beeble_object was designed for use with Functional Programming, it comes with some additional FP support.
Immutable Properties
One of the tenets of Functional Programming is, “don’t mutate state.” To support this, beeble_object creates immutable properties by default.
Magik> bo << beeble_object.new()
a beeble_object
Magik> bo.name << "Zaphod"
"Zaphod"
Magik> print(bo)
{"name":"Zaphod"}
Magik> bo.name << "Trillian"
**** Error: Cannot assign to immutable property NAME
error(string="Cannot assign to immutable property NAME")
Magik>
Notice how we can define a new property (in line 4) without issue, however if we try to assign a value to an existing property (line 10), an error is raised. This ensures we don’t accidentally mutate state — which is a source of major problems when it comes to introducing bugs and not being able to write meaningful tests.
If, however, we want to mutate state (in general, not a good idea, but sometimes it’s required), there’s the make_mutable() method.
Magik> bo.name << "Trillian"
**** Error: Cannot assign to immutable property NAME
error(string="Cannot assign to immutable property NAME")
Magik> bo.make_mutable(:name)
True
Magik> bo.name << "Trillian"
"Trillian"
Magik> print(bo)
{"name":"Trillian"}
Magik> bo.name << "Marvin"
**** Error: Cannot assign to immutable property NAME
error(string="Cannot assign to immutable property NAME")
Magik>
In line 5 we make the name property mutable and then we can assign a new value to it (line 8). However once that new value has been assigned, the property once again becomes immutable (as shown in line 14).
Removing Properties
If we want to remove a property from a beeble_object, we just set it to _unset as shown below in line 7.
We can also use the property_names method, as in line 12, to display the properties an object has (there is also a property_values method that returns a simple_vector containing the values in the beeble_object).
And if we need to rename a property, the boot operator (i.e. ^<<) provides a convenient way to accomplish this. Line 21, below, demonstrates how a property can be removed and its value assigned to a new property via boot.
Magik> print(zaphod)
{"gender":"Male","title":"President","race":"Betelgeusian","name":"Zaphod"}
Magik> bo.make_mutable(:title)
True
Magik> zaphod.title << _unset
Magik> print(zaphod)
{"gender":"Male","race":"Betelgeusian","name":"Zaphod"}
Magik> print(zaphod.property_names)
simple_vector(1,3):
1 :gender
2 :race
3 :name
Magik> bo.make_mutable(:gender)
True
Magik> zaphod.sex << zaphod.gender ^<< _unset
Magik> print(zaphod)
{"sex":"Male","race":"Betelgeusian","name":"Zaphod"}
Magik>
Sealing an Object
To further combat state mutation, it’s possible to seal a beeble_object. This means no additional properties can be defined on the sealed objects nor can any existing properties be deleted.
However it is possible to change existing properties (if the make_mutable() method is used).
Magik> print(bo)
{"name":"Trillian"}
Magik> bo.title << "President of the Galaxy"
"President of the Galaxy"
Magik> print(bo)
{"title":"President of the Galaxy","name":"Trillian"}
Magik> bo.seal
True
Magik> print(bo)
{"title":"President of the Galaxy","name":"Trillian"}
Magik> bo.ship << "Heart of Gold"
**** Error: Sealed object is not extensible. Cannot add new property SHIP
error(string="Sealed object is not extensible. Cannot add new property SHIP")
Magik> print(bo)
{"title":"President of the Galaxy","name":"Trillian"}
Magik> bo.title << _unset
**** Error: Sealed object's property TITLE cannot be deleted
error(string="Sealed object's property TITLE cannot be deleted")
Magik> print(bo)
{"title":"President of the Galaxy","name":"Trillian"}
Magik> bo.make_mutable(:title)
True
Magik> bo.title << "Girlfriend of Zaphod"
"Girlfriend of Zaphod"
Magik> print(bo)
{"title":"Girlfriend of Zaphod","name":"Trillian"}
Magik>
We sealed the object in line 10, so when we attempt to add a new property (ship in line 16), an error is raised and the new property is not added.
Similarly, when we try to delete an existing property (title in line 23), an error is raised and the property is not removed.
However we can still update existing properties (line 33) as long as we make them mutable first (as in line 30).
Once an object has been sealed, it cannot be unsealed.
Freezing an Object
And if we really want to combat mutation at its highest level, we can freeze a beeble_object.
Frozen objects cannot have new properties added or have existing properties modified or deleted.
Magik> bo.freeze
True
Magik> print(bo)
{"title":"Zaphods girlfriend","name":"Trillian"}
Magik> bo.ship << "Heart of Gold"
**** Error: Frozen object is not extensible. Cannot add or modify property SHIP
error(string="Frozen object is not extensible. Cannot add or modify property SHIP")
Magik> bo.title << _unset
**** Error: Frozen object's property TITLE cannot be deleted
error(string="Frozen object's property TITLE cannot be deleted")
Magik> bo.make_mutable(:title)
True
Magik> bo.title << "Marvin's friend"
**** Error: Frozen object is not extensible. Cannot add or modify property TITLE
error(string="Frozen object is not extensible. Cannot add or modify property TITLE")
Magik>
Notice that because we froze the object in line 1, we can’t add a new property (line 7) or remove an existing property (line 11). We also can’t modify an existing property — as shown in line 18. If we attempt any of these actions, an error is raised.
Frozen objects give the highest level of protection against data being accidentally mutated. If we want to ensure an object is read-only, then freezing it is a good option. Not only does this allow us to automatically catch errors during development, but it also communicates our intention about the object to other developers who may later be working on the code.
Similar to seal, once an object is frozen, it cannot be unfrozen.
A Warning…
Also keep in mind that immutable properties, as well as sealed and frozen objects, only apply to top level properties — so if a property points to, say, a rope, then the rope can be modified even if the object is sealed or frozen (or the property is immutable). The actual rope can’t be removed or changed to something else, but its contents can be.
Therefore I recommend always using a beeble_object or simple_vector to store collections of data. Since lower-level beeble_objects can be frozen or sealed, and a simple_vector can’t be modified, these objects provide a safer way to write applications.
Hooks
The beeble_object allows us to define hooks that fire when a certain type of method is invoked. Method types fall into two categories: getter and setter.
When a property is set on a beeble_object, the setter method is invoked. When a property is retrieved, the getter method is invoked.
Hooks allow us to overcome bad design practices. Usually we want properties to be private and accessed only through getter and setter methods. Public properties that can be changed without the object knowing about the change can lead to buggy code that is difficult to test.
However there are a number of use cases where the beeble_object is simply acting as a container and doesn’t necessarily need to know when a property is set or retrieved. A good example of this is when we’re packaging arguments into an object and passing them to a method.
On the other hand, there are use cases where we shouldn’t be changing or accessing properties without giving the object a chance to respond to what’s happening. Examples include setting properties that need to be validated or accessing a property that must invoke additional functionality. In these instances we can use hooks.
Look at the examples below.
Magik> marvin << beeble_object.new()
a beeble_object
# setter method invoked.
Magik> marvin.personality << "depressed"
"depressed"
# setter method invoked.
Magik> marvin.type << "robot"
"robot"
# getter method invoked.
Magik> marvin.type
"robot"
Magik>
When a method type (i.e. getter or setter) is invoked, we can hook into it using special hook methods on the beeble_object class. These methods are named hook_getter and hook_setter. They don’t exist by default, so no hooks are run.
However if we define the hooks, they will be executed whenever a get or set operation is invoked on the beeble_object.
Look at the following code.
Magik> marvin.hook_getter << _proc(p_val, p_method_name, p_self) write("Value: ", p_val, " Method Name: ", p_method_name, " Self: ", p_self) _endproc
Magik> marvin.hook_setter << _proc(p_val, p_method_name, p_self, p_new_value) write("Old Value: ", p_val, " Method Name: ", p_method_name, " Self: ", p_self, " New Value: ", p_new_value) _endproc
Magik> marvin.friend << "arthur"
Old Value: unset Method Name: friend<< Self: a beeble_object New Value: arthur
"arthur"
Magik> marvin.friend << "trillian"
Old Value: arthur Method Name: friend<< Self: a beeble_object New Value: trillian
"trillian"
Magik> marvin.friend
Value: trillian Method Name: friend Self: a beeble_object
"trillian"
Magik>
$
In line 1 we set the special hook_getter hook method. Note this method receives the current value of the property, the name of the method that was invoked as well as a reference to the object (i.e. _self). In this case we simply write out a message displaying the three parameters.
In line 4 we do the same thing for the hook_setter hook, but this method receives an additional parameter — the new value to be set.
Then, in line 7, we invoke a setter and see the hook_setter proc’s message displayed (line 8). Note the old value is _unset because this is the first time we’re setting the property (so it’s value before setting it defaults to _unset).
In line 12 we change the friend property’s value again. Now notice (line 13) the old value returned is, “arthur” because that was the current value before setting the new value to, “trillian”.
In line 17 we retrieve the friend property thereby invoking the hook_getter method (whose results are shown in line 18). Of course we could have just as easily done all sorts of cool things in that method, such as sending a RESTful Service request or emitting an event so any subscribed observers are notified the property was accessed.
Now that’s all pretty neat, but is it really that useful?
Well, yes… it is, and you’re limited only by your imagination. I’ll demonstrate one of my favourite techniques that combine hooks and observables to create an extremely useful pattern. It opens the door to a new way of implementing loosely coupled components.
Take a look at this…
Magik> marvin.hook_getter << _proc @hook_getter(p_value, p_method, p_self) _self.emit(:beeble_object, p_method) _endproc
proc hook_getter(p_method)
Magik> marvin.friend
"trillian"
Magik> obs << beeble_observable.new(marvin.hook_getter)
a beeble_observable
Magik> obs.subscribe(_proc @observer(e) write("observer: ", e.data) _endproc)
a beeble_observable
Magik> marvin.friend
observer: friend
"trillian"
Magik> obs.unsubscribe()
Magik> marvin.friend
"trillian"
Magik>
$
In line 1 we redefine the hook_getter method to emit an event. Then in line 5 we invoke a getter. It returns what we’d expect (line 6).
Now in line 9 we create an observable based on the hook_getter method and subscribe to it (line 13), passing in an observer proc as the callback.
When we now invoke the getter (line 17), we see the observer proc is run. If we unsubscribe (line 22) and then invoke the getter again (line 25), the observer doesn’t run.
We’ve achieved a high degree of loose coupling between the beeble_object and the observer proc, and that enables us to do a host of interesting things without the baggage and expense that comes along with tightly coupled components.
One final thing to note is hooks only run on objects where they are defined — they aren’t inherited by children of prototypes. So in the following code…
Magik> bo << beeble_object.new()
a beeble_object
Magik> bo.hello << "hi"
"hi"
Magik> bo.prototype << marvin
a beeble_object
Magik> obs.subscribe(_proc @observer(e) write("observer: ", e.data) _endproc)
a beeble_observable
Magik> marvin.type
observer: type
"robot"
Magik> bo.type
observer: type
"robot"
Magik> bo.hello
"hi"
Magik>
$
…we see a local property defined on a new beeble_object (line 4). Then we set the object’s prototype (line 7) and subscribe as before (line 10). Now when we invoke a getter (line 13) we see the hook_getter is invoked. Similarly when we invoke a getter for a property that doesn’t exist on the new object (but does exist on the prototype), line 17, the hook_getter is also invoked because it runs on the prototype.
However if we invoke a getter on a property that does exist on the new object (line 21), the hook_getter is not invoked because it is not defined on the new object.
this has been Eliminated
Oh, and before I forget, JavaScript objects have the concept of this (which is roughly analogous to _self in Magik), but beeble_object doesn’t have this because it was developed to support Functional Programming (FP), and in the FP paradigm we don’t want references to instances of beeble objects via this because it causes all sorts of problems and can lead to lots of boilerplate code.
Instead we create objects, encapsulating public and private data and methods, using closures and factories. I’ve explained why closures are better for defining private properties in another article, so feel free to click on over there for a deep dive into the reasons.
As an aside, this in JavaScript may surprise you because it can take on different values depending on where it is used. The value of this is established at run-time and depends on how the function is invoked, not where the function was defined in the code. Therefore it doesn’t always point to a particular object and it can contain different values depending on how a function is defined (e.g. using the function keyword versus using an arrow function), but fortunately we don’t have to worry about this with beeble_object.
An Alternative to if/then/else
I’ll end with an example that shows how to create a dynamic version of if/then/else statements. Not only do we get rid of long strings of if statements, but the dynamic nature ensures we don’t have to open the source code to add or remove cases.
Look at the example below.
_global nested_ifs<<
_proc @nested_ifs(p_selector)
_if p_selector _is :one
_then
write("the value is one.")
_elif p_selector _is :two
_then
write("the value is two.")
_elif p_selector _is :three
_then
write("the value is three.")
_elif p_selector _is :four
_then
write("the value is four.")
_elif p_selector _is :five
_then
write("the value is five.")
_else
write("default value")
_endif
_endproc
This is a standard pattern for handling multiple cases and is not the easiest thing to read. Using beeble_object, we can rewrite the procedure as follows.
_global better_selector<<
_proc @better_selector(p_selector)
_constant SELECTIONS << beeble_object.new()
SELECTIONS.one << "the value is one."
SELECTIONS.two << "the value is two."
SELECTIONS.three << "the value is three."
SELECTIONS.four << "the value is four."
SELECTIONS.five << "the value is five."
write(SELECTIONS.perform(p_selector).default("default value"))
_endproc
Note how we load a beeble_object with values (but we could also have used methods) and if p_selector matches a property name, the corresponding value is displayed using the perform() method. It’s so much easier to read through.
If p_selector does not match any keys, _unset is returned — which we handle using the default method in line 12 (equivalent to the else statement in the previous example).
And take note that if we had defined SELECTIONS on a shared constant in a class, we could then dynamically add or remove options without having to open the code, make changes and recompile.
It’s another example of beeble_object’s power and flexibility.
As you can see, a prototypal object is quite useful in a number of different scenarios and that’s why I implemented it in Magik. Whereas I would once use a property_list or concurrent_hash_map, with their clunky bracket notation APIs, I now use a beeble_object — and I get all its other goodies for free.
The extent of its usefulness has really surprised me, and I’m constantly discovering new ways to use it to make applications simpler and easier to read — so I guess those JavaScript guys knew what they were doing.
I’ve written a number of other articles on this site and you may notice the example code is littered with beeble_object. Rather than explain it in each article, I figured it is more efficient to describe it here and just throw in links when appropriate.
I hope you can see how useful it can be in a variety of situations. Combined with Magik’s exemplar-based objects, beeble_object expands the realm of possibilities when working with Magik.