The Coming of User-Defined Types


#1

The interesting modification of / into a 0-element PATH! makes paths a more solid and “pleasing” part, and clarifies that / is not a natural WORD! character.

Central to making the trick palatable is to change path dispatch for numbers to do division. But there’s currently no way for users to extend or modify path dispatch. This means there’s no way to define / to do anything but field selection out of an OBJECT!.

But @giuliolunati wants to use / for division on a matrix type. He’s done some improvised attempts to create matrix as an object, which so far has relied on overriding the previous WORD!-based / to recognize his matrix objects.

So to keep his code working, he needs some kind of hook for defining pathing. But really this is all part of the can-of-worms of user-defined types. Very little work has been done in this area; Rebol is a mostly evaluator-driven experiment, and hasn’t had a story for multiple dispatch or overloading or anything like that.

So first, let’s be clear…

R3-Alpha user defined types (UTYPE!) were vaporware

All that existed of user-defined datatypes in R3-Alpha was a cell payload definition (“REBUDT”), which was essentially two objects–the content (data/methods) and an additional object. Then there was about a paragraph’s worth of code.

The code doesn’t appear to even be trying to work. :-/ But the gist is that you’d provide functions for each “type action”. A prototype object with a NONE! for each action name was initialized in Init_Utype_Proto(), and your user defined type would run through the object creation process inheriting from that.

So maybe you’d say something like:

matrix: make utype! compose/deep [
   [append: (func [matrix value] [print "this gets called on append"])] ;-- spec part
   [rows: 2 columns: 2 data: [[0 0] [0 0]]] ;-- data part
]
append matrix [10 10] ;-- perhaps should add a row to the matrix?  :-/

But even if that worked, there’s a boatload of unanswered questions. There was a stub for where molding/forming/comparison would provide hooks, but those stubs were empty. How would path dispatch work? Is every user defined datatype just a UTYPE! or can you do some finer-grained type checking?

Red recognizes methods with certain names

When it comes to being able to “hook” a datatype, Red has at least one example mentioned when they added object support. It was set up so that if you had a method called on-change* it would call it when the object changed. Here’s an example from their page:

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]
        ]
    ]
]

This doesn’t put the specially dispatched methods into another namespace (e.g. the “second object” of the UTYPE!'s REBUDT cell). This puts them in-band. So you couldn’t have an object with field called on-change* that was just a regular data member.

I don’t know how many of these they’re planning on adding, but the idea of reserving names in the space where ordinary data fields live seems suspect to me. It might seem harmless as there’s no reason to have a plain data member called on-change* at the outset. But whenever you introduce a meta thing like this you wind up wanting to make other objects that mention it, so on-change*: true can become interesting when making something that lists the meta methods something has–for example.

Ren-C added out-of-band META feature

R3-Alpha’s MODULE! data type was very much like an OBJECT!, but instead of just having one set of keys and values it had two. The second set of information held things like the module’s title, list of exports…basically anything that it was keeping track of that it got out of the header. But by putting it in a separate place, it wasn’t stealing any potential key names from the module’s content. (e.g. if you put it in a field called “header”, you couldn’t have a top-level declaration in the module body called “header”)

In Ren-C, this module-only ability was turned into something more generalized, which allowed an ANY-CONTEXT! to have an associated “meta” context. So this applied not just to MODULE!, but to OBJECT!, ERROR!, PORT!, and FRAME!. It seemed like a step in the right direction.

Could the META context store type information?

The thing that the meta object has in common with UTYPE! is that it’s a second context associated with an ANY-CONTEXT!, whose fields don’t interfere. Should it be the place to look for methods the system might call, such as on-change*…or something for path dispatch, like on-path*?

This also raises some questions about how meta information should be handled in terms of COPY. Does copying an object give you the same meta information, a copy of the meta information (deep? shallow?) or does the new object not have any meta information at all? Most of those questions got punted on, but could be informed by what would be needed for the type handling.

Solving user-defined types is a big area, given that there really is no precedent in Rebol’s class of languages. We don’t know how to say b: make book! [author: {...} isbn: #...] and then say type of b and get BOOK! back, or make a function like checkout: function [b [book!]] [...]. But neither does JavaScript (its JavaScript typeof only has 9 possibilities). Nor does Ruby or Python.

For now, we need a hack for doing path processing, so I’ll just have to come up with something for that. But I wanted to put down some notes about the state of things.