Question

I usually program by functions in an "instinctive" manner, but my current problem can be easily solved by objects, so I go ahead with this method.

Doing so, I am trying to find a way to give an object a constructor method, the equivalent of init() in python, for example.

I looked in the http://www.rebol.com/docs/core-fr/fr-index.html documentation, but I couldn't find anything relevant.

Was it helpful?

Solution 3

Actually, by reading again the Rebol Core documentation (I just followed the good old advice: "Read The French Manual"), there is another way to implement a constructor, quite simple:

http://www.rebol.com/docs/core-fr/fr-rebolcore-10.html#section-8

Of course it is also in The English Manual:

http://www.rebol.com/docs/core23/rebolcore-10.html#section-7

=>

Another example of using the self variable is a function that clones itself:

person: make object! [
    name: days-old: none
    new: func [name' birthday] [
        make self [
            name: name'
            days-old: now/date - birthday
        ]
    ]
]

lulu: person/new "Lulu Ulu" 17-May-1980

print lulu/days-old
7366

I find this quite convenient, and this way, the constructor lies within the object. This fact makes the object more self-sufficient.

I just implemented that successfully for some geological stuff, and it works well:

>> source orientation
orientation: make object! [
    matrix: []
    north_reference: "Nm"
    plane_quadrant_dip: ""
    new: func [{Constructor, builds an orientation object! based on a measurement, as given by GeolPDA device, a rotation matrix represented by a suite of 9 values} m][
        make self [
            foreach [a b c] m [append/only matrix to-block reduce [a b c]] 
            a: self/matrix/1/1 
            b: self/matrix/1/2 
            c: self/matrix/1/3 
            d: self/matrix/2/1 
            e: self/matrix/2/2 
            f: self/matrix/2/3 
            g: self/matrix/3/1 
            h: self/matrix/3/2 
            i: self/matrix/3/3 
            plane_normal_vector: reduce [matrix/1/3 
                matrix/2/3 
                matrix/3/3
            ] 
            axis_vector: reduce [self/matrix/1/2 
                self/matrix/2/2 
                self/matrix/3/2
            ] 
            plane_downdip_azimuth: azimuth_vector plane_normal_vector 
            plane_direction: plane_downdip_azimuth - 90 
            if (plane_direction < 0) [plane_direction: plane_direction - 180] 
            plane_dip: arccosine (plane_normal_vector/3) 
            case [
                ((plane_downdip_azimuth > 315) or (plane_downdip_azimuth <= 45)) [plane_quadrant_dip: "N"] 
                ((plane_downdip_azimuth > 45) and (plane_downdip_azimuth <= 135)) [plane_quadrant_dip: "E"] 
                ((plane_downdip_azimuth > 135) and (plane_downdip_azimuth <= 225)) [plane_quadrant_dip: "S"] 
                ((plane_downdip_azimuth > 225) and (plane_downdip_azimuth <= 315)) [plane_quadrant_dip: "W"]
            ] 
            line_azimuth: azimuth_vector axis_vector 
            line_plunge: 90 - (arccosine (axis_vector/3))
        ]
    ]
    repr: func [][
        print rejoin ["Matrix: " tab self/matrix 
            newline 
            "Plane: " tab 
            north_reference to-string to-integer self/plane_direction "/" to-string to-integer self/plane_dip "/" self/plane_quadrant_dip 
            newline 
            "Line: " tab 
            rejoin [north_reference to-string to-integer self/line_azimuth "/" to-string to-integer self/line_plunge]
        ]
    ]
    trace_te: func [diagram [object!]][
        len_queue_t: 0.3 
        tmp: reduce [
            plane_normal_vector/1 / (square-root (((plane_normal_vector/1 ** 2) + (plane_normal_vector/2 ** 2)))) 
            plane_normal_vector/2 / (square-root (((plane_normal_vector/1 ** 2) + (plane_normal_vector/2 ** 2))))
        ] 
        O: [0 0] 
        A: reduce [- tmp/2 
            tmp/1
        ] 
        B: reduce [tmp/2 0 - tmp/1] 
        C: reduce [tmp/1 * len_queue_t 
            tmp/2 * len_queue_t
        ] 
        L: reduce [- axis_vector/1 0 - axis_vector/2] 
        append diagram/plot [pen black] 
        diagram/trace_line A B 
        diagram/trace_line O C 
        diagram/trace_line O L
    ]
]
>> o: orientation/new [0.375471 -0.866153 -0.32985 0.669867 0.499563 -0.549286 0.640547 -0.0147148 0.767778]
>> o/repr
Matrix:     0.375471 -0.866153 -0.32985 0.669867 0.499563 -0.549286 0.640547 -0.0147148 0.767778
Plane:  Nm120/39/S
Line:   Nm299/0

Another advantage of this way is that variables defined by the "new" method directly belongs to the object "instance" (I ran into some trouble, with the other methods, having to mention self/ sometimes, having to initialize variables or not).

OTHER TIPS

There is no special constructor function in Rebol, but there is a possibility to write ad hoc init code if you need it on object's creation in the spec block. For example:

a: context [x: 123]

b: make a [
    y: x + 1
    x: 0
]

So, if you define your own "constructor" function by convention in the base object, you can call it the spec block on creation. If you want to make it automatic, you can wrap that in a function, like this:

a: context [
    x: 123
    init: func [n [integer!]][x: n]
]

new-a: func [n [integer!]][make a [init n]]

b: new-a 456

A more robust (but bit longer) version of new-a that would avoid the possible collision of passed arguments to init with object's own words would be:

new-a: func [n [integer!] /local obj][
    also 
        obj: make a []
        obj/init n
]

You could also write a more generic new function that would take a base object as first argument and automatically invoke a constructor-by-convention function after cloning the object, but supporting optional constructor arguments in a generic way is then more tricky.

Remember that the object model of Rebol is prototype-based (vs class-based in Python and most other OOP languages), so the "constructor" function gets duplicated for each new object created. You might want to avoid such cost if you are creating a huge number of objects.

To my knowledge, there is no formal method/convention for using object constructors such as init(). There is of course the built-in method of constructing derivative objects:

make prototype [name: "Foo" description: "Bar"]
    ; where type? prototype = object!

My best suggestion would be to define a function that inspects an object for a constructor method, then applies that method, here's one such function that I've proposed previously:

new: func [prototype [object!] args [block! none!]][
    prototype: make prototype [
        if in self 'new [
            case [
                function? :new [apply :new args]
                block? :new [apply func [args] :new [args]]
            ]
        ]
    ]
]

The usage is quite straightforward: if a prototype object has a new value, then it will be applied in the construction of the derivative object:

thing: context [
    name: description: none
    new: [name: args/1 description: args/2]
]

derivative: new thing ["Foo" "Bar"]

note that this approach works in both Rebol 2 and 3.

I'm trying to find out how OO works in REBOL. Prototypical indeed. Yesterday I came across this page, which inspired me to the classical OO model below, without duplication of functions:

;---- Generic function for class or instance method invocation ----;
invoke: func [
    obj  [object!]
    fun  [word!]
    args [block!]
][
    fun: bind fun obj/.class
    ;---- Class method names start with a dot and instance method names don't:
    unless "." = first to-string fun [args: join args obj]
    apply get fun args
]

;---- A class definition ----;
THIS-CLASS: context [
    .class: self                           ; the class refers to itself

    ;---- Class method: create new instance ----;
    .new: func [x' [integer!] /local obj] [
        obj: context [x: x' .class: none]  ; this is the object definition
        obj/.class: self/.class            ; the object will refer to the class
                                           ; it belongs to
        return obj
    ]

    ;---- An instance method (last argument must be the instance itself) ----;
    add: func [y obj] [
        return obj/x + y
    ]
]

Then you can do this:

;---- First instance, created from its class ----;
this-object: THIS-CLASS/.new 1
print invoke this-object 'add [2]

;---- Second instance, created from from a prototype ----;
that-object: this-object/.class/.new 2
print invoke that-object 'add [4]

;---- Third instance, created from from a prototype in another way ----;
yonder-object: invoke that-object '.new [3]
print invoke yonder-object 'add [6]

;---- Fourth instance, created from from a prototype in a silly way ----;
silly-object: yonder-object/.class/.class/.class/.class/.new 4
print silly-object/.class/add 8 silly-object
print this-object/.class/add 8 silly-object
print THIS-CLASS/add 8 silly-object

(It works in REBOL 2, and prints 3, 6, 9, 12, 12, 12 successively.) Hardly any overhead. Probably it won't be difficult to find a dozen of other solutions. Exactly that is the real problem: there are too many ways to do it. (Maybe we'd better use LoyalScript.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top