December 22, 2014

0.5.0: Objects support

We are bumping the version number up higher as we are bringing a new foundational layer and important construct to Red: object! datatype and contexts support.

Supporting objects in the Red interpreter is relatively easy and straightforward. But adding those features in the compiler has proven to be more complex than expected, especially for access-path support, paths being especially tricky to process, given their highly dynamic nature. Though, I have pushed Red beyond the edges I was planning to stop at for objects support, and the result so far is really exciting!

Object model

Just a short reminder mainly intended for newcomers. Red implements the same object concept as Rebol, called prototype-based objects. Creating new objects is done by cloning existing objects or the base object! value. During the creation process, existing field values can be modified and new fields can be added. It is a very simple and efficient model to encapsulate your Red code. There is also a lot to say about words binding and contexts, but that topic is too long for this blog entry, we will address that in the future documentation.

Object creation

The syntax for creating a new object is:
    make object! <spec>
 
    <spec>: specification block
Shorter alternative syntaxes (just handy shortcuts):
    object  <spec>
    context <spec>
The specification block can contain any valid Red code. Words set at the root level of that block will be collected and will constitute the new object's fields.

Example:
    make object! [a: 123]
    
    object [a: 123 b: "hello"]
    
    c: context [
       list: []     
       push: func [value][append list value]
    ]
You can put any valid code into a specification block, it will be evaluated during the object construction, and only then.

Example:
    probe obj: object [
        a: 123
        print b: "hello"
        c: mold 3 + 4
    ]
will output:
    hello
    make object! [
        a: 123
        b: "hello"
        c: "7"
    ]
Objects can also be nested easily:
    obj: object [
        a: 123
        b: object [
            c: "hello"
            d: object [
                data: none
            ]
        ]
    ]

Another way to create an object is to use the copy action which does not require a specification block, so does just a simple cloning of the object. Existing functions will be re-bound to the new object.

Syntax:
    copy <object>
Object access paths

In order to access object fields, the common path syntax is used (words separated by a slash character). Each word (or expression) in a path is evaluated in the context given by the left side of the path. Evaluation of a word referring to a function will result in invoking the function, with its optional refinements.

Example:
    book: object [
        title: author: none
        show: does [print [mold title "was written by" author]]
    ]

    book/title: "The Time Machine"
    book/author: "H.G.Wells"
    print book/title
    book/show
will output:
    The Time Machine
    "The Time Machine" was written by H.G.Wells
SELF reference

A special keyword named self has been reserved when self-referencing the object is required.

Example:
    book: object [
        title: author: none
        list-fields: does [words-of self]
    ]
    book/list-fields
will output:
    [title author list-fields]
Object inheritance

While cloning produces exact replicas of the prototype object, it is also possible to extend it in the process, using make action.

Syntax:
    make <prototype> <spec>

    <prototype> : object that will be cloned and extended
    <spec>      : specification block
Example:
    a: object [value: 123]
    
    c: make a [
        increment: does [value: value + 1]
    ]
    
    print c/increment
    print c/increment
will output:
    124
    125
It is also possible to use another object as <spec> argument. In such case, both objects are merged to form a new one. The second object takes priority in case both objects share same field names.
 
Example:
    a: object [
        value: 123
        show: does [print value]
    ]
    b: object [value: 99]
    
    c: make a b
    c/show
will output:
    99
Detecting changes in objects

Sometimes, it can be very useful to detect changes in an object. Red allows you to achieve that by defining a function in the object that will be called just after a word is set. This event is generated only when words are set using a path access (so inside the object, you can set words safely). This is just a first incursion in the realm of metaobject protocols, we will extend that support in the future.

In order to catch the changes, you just need to implement the following function in your object:
    on-change*: func [word [word!] old new][...]
    
    word : field name that was just affected by a change
    old  : value referred by the word just before the change
    new  : new value referred by the word
It is allowed to overwrite the word just changed if required. You can directly set the field name or use set:
    set word <value>
Example:
    book: object [
        title: author: year: none
  
        on-change*: func [word old new /local msg][
            if all [
                word = 'year
                msg: case [
                    new >  2014 ["space-time anomaly detected!"]
                    new < -3000 ["papyrus scrolls not allowed!"]
                 ]
            ][
               print ["Error:" msg]
            ]
        ]
    ]

    book/title: "Moby-Dick"
    book/year: -4000
will output:
    Error: papyrus scrolls not allowed!
Extended actions and natives for objects

You can use set on an object to set all fields at the same time. get on an object will return a block of all the fields values. get can also be used on a get-path!.

Example:
    obj: object [a: 123 b: "hello"]
    probe get obj
    set obj none
    ?? obj
    set obj [hello 0]
    ?? obj
    probe :obj/a
will output:
    [123 "hello"]
    obj: make object! [
        a: none
        b: none
    ]
    obj: make object! [
        a: 'hello
        b: 0
    ]
    hello

Find action gives you a simple way to check for a field name in an object. If found it will return true, else none.

Select action does the same check as find, but returns the field value for matched word.

    obj: object [a: 123]
    probe find obj 'a
    probe select obj 'a
    probe find obj 'hello
will output:
    true
    123
    none
The in native will allow you to bind a word to a target context:
    a: 0
    obj: object [a: 123]
    probe a
    probe get in obj 'a
will output:
    0
    123

Bind native is also available, but not completly finished nor tested.

Reflectors

Some reflective functions are provided to more easily access objects internal structure.

  • words-of returns a block of field names.
  • values-of returns a block of field values.
  • body-of returns the object's content in a block form.

Example:
     a: object [a: 123 b: "hello"]
     probe words-of a
     probe values-of a
     probe body-of a
will output:
    [a b]
    [123 "hello"]
    [a: 123 b: "hello"]
SYSTEM object

The system object is a special object used to hold many values required by the runtime library. You can explore it using the new extended help function, that now accepts object paths.
red>> help system
`system` is an object! of value:
    version          string!   0.5.0
    build            string!   21-Dec-2014/19:27:05+8:00
    words            function! Return a block of global words available
    platform         function! Return a word identifying the operating system
    catalog          object!   [datatypes actions natives errors]
    state            object!   [interpreted? last-error]
    modules          block!    []
    codecs           object!   []
    schemes          object!   []
    ports            object!   []
    locale           object!   [language language* locale locale* months da...
    options          object!   [boot home path script args do-arg debug sec...
    script           object!   [title header parent path args]
    standard         object!   [header]
    view             object!   [screen event-port metrics]
    lexer            object!   [make-number make-float make-hexa make-char ...
Note: not all system fields are yet defined or used.

Future evolutions

As this release already took a lot of time, some of the planned features are postponed to future releases. Here are a few of them.

Sometimes, it is convenient to be able to add fields to an object in-place, without having to recreate it, losing lexical binding information in the process. To achieve that, a new extend native will be added, working like originaly intended in Rebol3.

In order to help the Red compiler produce shorter and faster code, a new #alias compilation directive will be introduced. This directive will allow users to turn an object definition into a "virtual" type that can be used in type spec blocks. For example:
    #alias book!: object [
        title: author: year: none
        banner: does [form reduce [author "wrote" title "in" year]]
    ]
    
    display: func [b [book!]][
        print b/banner
    ]
This addition would not only permit finer-grained type checking for arguments, but also help the user better document their code.

Another possible change will be in the output mold produces for an object. Currently such output will start with "make object!", this might be changed to just "object", in order to be shorter and easier to read in addition to be more consistent to the way function! values are molded.

Fixed issues

In order to make this release happen as quickly as possible, we have not fixed all the open tickets that were planned to be fixed in this release, but we still managed to fix a few of them. The other pending tickets will be fixed in the upcoming minor releases.

I should also mention that 537 new tests were added to cover objects features. The coverage is already good, but we probably need more of them to cover edge cases.

That's all for this blog article! :-)

I will publish another blog entry about additional information regarding the implementation strategy used by the compiler for supporting contexts and object paths.

As we have almost completed other significant features during the last months, you should expect new minor releases happening very quickly in the next weeks. They will include:

  • New cross-platform console engine written entirely in Red (no dependencies).
  • New Android toolchain for creating APK files 100% ported to Red (no dependencies).
  • Full error and exceptions support at Red level.
  • Redbin initial implementation (not started yet).

Also, the work for 0.6.0 has started already (GUI support), even if its at prototype stage right now. I plan to release a first minimal version in the next few weeks (we will extend it step by step until 1.0).

Hope the waiting for the new release was worth it. ;-)

4 comments:

  1. "New Android toolchain for creating APK files 100% ported to Red (no dependencies)."

    ^-- That! The console is already better than Rebol 3, so hopefully this big selling point will be not held off for long from distractions of such features... could that be 0.5.1?

    Important work, guys! I will save my nitpicking for at least a day or two. :-)

    ReplyDelete
    Replies
    1. The current Red console has some external dependencies on Linux and Mac that can be very annoying to users (like libreadline 32-bit on 64-bit systems). Also, it lacks basic features like TAB completion or ESC key handling for breaking execution. The new console will solve all that, and provide us with a native cross-platform solution that can have any kind of backend.

      For the Android toolchain, we still are working on making it even more insanely great, but there is no hurry on releasing it. A working VID/View prototype for Android also needs to be ready in order to have any purpose for such toolchain. Fortunately, that's also not very far ahead.

      Delete

Fork me on GitHub