I realized recently that it is possible to implement message passing ala Arubic as a library. This has multiple advantages. For starters, the core can potentially be simpler, since it doesn't need to worry about such things, and objects using message passing can blend in seamlessly with non-message passing objects. For instance, right now hash tables in Arc are, well, hash tables. Ordinarily, trying to implement something that is like a table is difficult, which is what message passing is trying to solve. But in order to be really seamless, hash tables would also need to use message passing, and thus it would require 2 hash tables and about 5 functions per hash table... what a waste. What would be better is if hash tables could stay exactly the same, yet still allow for message passing to mimic tables. In his essays, pg said that he was relieved that he only needed to add type, tag, and rep to the language, and OOP could be implemented as a library with those three basic functions. At first I thought that was impossible, that something like Arubic would need to be built into the core, but now I realize that it is possible to get seamlessness[1] with those three functions, and in addition, nothing in the core needs to change, with one possible exception[2] So, with that revelation, I created a simple library called "object.arc"[3]. How it works is pretty simple: it creates an (object) macro that is like (obj) except it's return value is annotated with 'object: (object a 1 b 2) -> #(tagged object #hash((b . 2) (a . 1)))
(type (object)) -> table
What's that? It has a type of 'table? That's so objects can work with things like `each` without needing to extend them. You can override that by giving the object a `type` attribute.Oh, but now you can't tell whether it's a hash table or not... hm... wait, I know! You can use `object?` which can tell the difference: (object? (object)) -> t
I also provide some functions for manipulating objects: get-attribute, set-attribute, and del-attribute[4]. The names are cumbersome, but with proper ssyntax support, they could be very short.I then extend various low-level functions like sref, maptable, and type. Then I extend coerce to give it custom behavior when called in functional position. Now you can grab the keys of an object (just like a hash table), and also assign to it: (= foo (object a 1))
(foo 'a) -> 1
(keys foo) -> (a)
(= (foo 'b) "qux")
(keys foo) -> (a b)
Okay, big deal, hash tables can do that too. Now's when we get to the fun stuff. You can override those default behaviors just by giving the object appropriate attributes. For instance, let's say you want the object to do something when called: (= foo (object call (fn args
(prn "called with " args))))
(foo 'a 'b 'c) -> called with (a b c)
Or to do something when assigned to: (= foo (object set (fn (k v)
(prn "assigning " k " to " v))))
(= (foo 'a) "bar") -> assigning a to bar
There's also `keys`, and I plan to add in `del` later. You're free to add your own, of course.Lastly, there's a `print` attribute, which lets you customize how your object is displayed: (object print (fn () "%custom printing!%")) -> %custom printing!%
---* [1]: I have found one caveat: my implementation extends `type`, so if you load the "object.arc" library two or more times (in the same namespace), it will go into an infinite loop. But as long as you load it only once, it should be fine. I might be able to fix that. * [2]: If your favorite implementation of Arc supports something similar to defcall, then you can implement message passing. If your favorite implementation does not support defcall, then you most likely won't be able to. * [3]: ar does not provide something with the power of defcall, but my fork does. You can find my fork at https://github.com/Pauan/ar The "object.arc" library is in the "lib/" subdirectory. * [4]: Why del-attribute? Because unlike hash tables (in Arc), objects can have nil as both a key and a value, which is different from a lack of value: (= foo (object a nil))
(foo 'a (uniq)) -> nil
(foo 'b (uniq)) -> g459
Oh yeah, and if you ever want a hash table to contain nil, you can use get/set/del-attribute on hash tables too: (= foo (table))
(set-attribute foo nil 'a)
(set-attribute foo 'a nil)
foo -> #hash((nil . a) (a . nil))
|