is there an object constructor in rebol - oop

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.

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.

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).

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.)

Related

C-style for loops in REBOL

I attempted to write a C-style for-loop in REBOL:
for [i: 0] [i < 10] [i: i + 1] [
print i
]
This syntax doesn't appear to be correct, though:
*** ERROR
** Script error: for does not allow block! for its 'word argument
** Where: try do either either either -apply-
** Near: try load/all join %/users/try-REBOL/data/ system/script/args...
Does REBOL have any built-in function that is similar to a C-style for loop, or will I need to implement this function myself?
The equivalent construct in a C-like language would look like this, but I'm not sure if it's possible to implement the same pattern in REBOL:
for(i = 0; i < 10; i++){
print(i);
}
Because of the rebol3 tag, I'll assume this question pertains to Rebol 3.
Proposed "CFOR" for Rebol 3
For Rebol 3, there is a proposal (which got quite a bit of support) for a "general loop" very much along the lines of a C-style for and therefore currently going under the name of cfor as well: see CureCode issue #884 for all the gory details.
This includes a much refined version of Ladislav's original implementation, the current (as of 2014-05-17) version I'll reproduce here (without the extensive inline comments discussing implementation aspects) for the sake of easy reference:
cfor: func [ ; Not this name
"General loop based on an initial state, test, and per-loop change."
init [block! object!] "Words & initial values as object spec (local)"
test [block!] "Continue if condition is true"
bump [block!] "Move to the next step in the loop"
body [block!] "Block to evaluate each time"
/local ret
] [
if block? init [init: make object! init]
test: bind/copy test init
body: bind/copy body init
bump: bind/copy bump init
while test [set/any 'ret do body do bump get/any 'ret]
]
General problems with user-level control structure implementations in Rebol 3
One important general remark for all user-level implementation of control constructs in Rebol 3: there is no analogue to Rebol 2's [throw] attribute in R3 yet (see CureCode issue #539), so such user-written ("mezzanine", in Rebol lingo) control or loop functions have problems, in general.
In particular, this CFOR would incorrectly capture return and exit. To illustrate, consider the following function:
foo: function [] [
print "before"
cfor [i: 1] [i < 10] [++ i] [
print i
if i > 2 [return true]
]
print "after"
return false
]
You'd (rightly) expect the return to actually return from foo. However, if you try the above, you'll find this expectation disappointed:
>> foo
before
1
2
3
after
== false
This remark of course applies to all the user-level implementation given as answers in this thread, until bug #539 is fixed.
There is an optimized Cfor by Ladislav Mecir
cfor: func [
{General loop}
[throw]
init [block!]
test [block!]
inc [block!]
body [block!]
] [
use set-words init reduce [
:do init
:while test head insert tail copy body inc
]
]
The other control structure that most people would use in this particular case is repeat
repeat i 10 [print i]
which results in:
>> repeat i 10 [print i]
1
2
3
4
5
6
7
8
9
10
I generally do no use loop very often, but it can be used to a similar extent:
>> i: 1
>> loop 10 [print ++ i]
1
2
3
4
5
6
7
8
9
10
Those are some useful control structures. Not sure if you were looking for cfor but you got that answer from others.
I have implemented a function that works in the same way as a C for loop.
cfor: func [init condition update action] [
do init
while condition [
do action
do update
]
]
Here's an example usage of this function:
cfor [i: 0] [i < 10] [i: i + 1] [
print i
]
For simple initial value, upper limit and step, following works:
for i 0 10 2
[print i]
This is very close to C for loop.

Programming a sandbox environment in rebol a bit like spoon.net

http://spoon.net let's you execute desktop application by downloading them from the web. When you quit it restores the system.
On http://askpoweruser.com I'd like to do the same thing. My idea would be to persist the whole system hierarchy on disk and then restore it at the end of execution.
Is a single line of code would be enough (seems like too easy for such complex feature that's why I doubt :)):
save %system.txt system
what is serialize refinement ? would it be usefull in that case ?
to restore system would I then just do
load %system.txt
here is my object:
>> o: context [b: "b" f: does [do make function! [] [print ["a"]]] oo: context [a: 1]]
>> ?? o
== o: make object! [
b: "b"
f: func [][do make function! [] [print ["a"]]]
oo: make object! [
a: 1
]
]
change something in function f:
>> o/oo/a: 2
>> append second last second first next next next third :o "b"
>> o/f
== a b
>> save/all %t.r :o
>> p: load %t.r
>> ?? p
== p: make object! [
b: "b"
f: func [][do make function! [] [print ["a" "b"]]] ;<----
oo: make object! [
a: 2 ;<------
]
]
>> p/f
== a b ;<----
it looks everything is ok. But of course this is just a single test.
You can't currently save the entire Rebol image like this. You can serialize Rebol values by using 'mold/all and save values by using 'save. But AFAIK the serialization doesn't properly save functions inside objects.
You could use something like CryoPID:
http://cryopid.berlios.de/
That would work at the process level, and you could use it for things besides Rebol. But it would be OS-specific.

Rebol: why get-access-modifier is called twice whereas It should be called only once

When typing in rebol console
do read http://askcodegeneration.com/csharp/simple-class/
I get get-access-modifier called twice:
Access modifier:
1. private: member can be accessed only by code in the same class
2. protected: member can be accessed only by code in the same class or in a derived class
3. internal: member can be accessed only by code in the same assembly
4. public: member can be accessed by code in the same assembly or another assembly that references it choice (by default 1):
Access modifier:
1. private: member can be accessed only by code in the same class
2. protected: member can be accessed only by code in the same class or in a derived class
3. internal: member can be accessed only by code in the same assembly
4. public: member can be accessed by code in the same assembly or another assembly that references it choice (by default 1):
Whereas it is only mentioned once in the source code:
append fields-template-output form reduce [
(to-word get-access-modifier) field-layout
]
I really can't see why, can you ?
Original code here (Internet Archive)
Yes. There is only one call to it, but it's inside of a foreach. Your default is two fields, so you get asked twice. Enter more, you'll get asked more.
While you could (and probably should) do the obvious thing of saving it in a variable, Rebol has other ways. For instance you could compose the block of code:
foreach field-layout fields-layout COMPOSE/DEEP [
append fields-template-output " "
append fields-template-output form reduce [
to-word (get-access-modifier) field-layout
]
append fields-template-output ";"
append fields-template-output newline
]
The composition runs once, looks deep for the parentheses in the block, and evaluates the code. (Kind of how parse does when it sees parentheses). The rest is left alone. So the block with the substitutions done is what's passed into FOREACH to run the loop.
Just a nuance of how you could have a call that appears to be inside a loop and yet is executed only once. I wouldn't suggest using it for something like this.
What I would suggest is studying making things less redundant in your code, by learning some more Rebol primitives like REJOIN...which builds a series out of a block. The series type will match whatever the first type it sees is (or a string if the first element is not a series):
modifier: get-access-modifier ;-- called only once, stored in variable
foreach field-layout fields-layout [
append fields-template-output rejoin [
" "
(to-string modifier)
field-layout
";"
newline
]
]
To solve the problem I have used static var to detect that it is executed only once (thanks to Sunanda tips is it possible to have static variable inside a rebol function? ).
ask-params: function [config-file [file!] default-class-name default-fields] [
;config-file: %askcodegeneration.com/csharp/simple-class/simple-class.params.txt
either value? 'class-name [
ans: ask rejoin ["class name" " (by default " class-name ")" ": "]
if ans <> "" [class-name: ans]
][
class-name: default-class-name
ans: ask rejoin [{class name (default "} class-name {"): }]
if ans <> "" [class-name: ans]
]
either exists? it: config-file [
params-block: copy load it
][
params-block: []
]
either res: find params-block class-name [
fields: pick res 2
print [ class-name "found in" it]
][
fields: default-fields
ans: ask rejoin [{fields (by default } {"} fields {"} {): }]
if ans <> "" [fields: ans]
new-param: reduce [
mold class-name
mold fields
]
either not exists? config-file [
create-directory "askcodegeneration.com/csharp/simple-class/"
write/append config-file reform new-param
][
write/append config-file reform [newline new-param]
]
]
append ret-value: [] class-name
append ret-value fields
ret-value
]

Shared function in Rebol (implementation inheritance)

Someone said you can have (implementation inheritance) with Rebol using get. So I tried:
shape: context [
x: 0
y: 0
draw: func['object][
probe get object
]
]
circle: make shape [
radius: 10
draw: get in shape 'draw
]
rectangle: make shape [
draw: get in shape 'draw
]
I want to pass the object by reference not by value so I pass only the name using 'Object. But then I have to call it like this
circle/draw 'circle
which is rather lame as I need to repeat the name circle twice while in usual inheritance there is the this keyword which avoid this kind of unatural syntax. Is there a more elegant way ?
Thanks.
There is a self word. At the risk of creating a false sense of certainty about that, I'll give you an example that presumably does what you want:
shape: make object! [
x: 0
y: 0
draw: func [object [object!]] [
probe object
]
]
circle: make shape [
radius: 10
draw: func [] [
shape/draw self
]
]
rectangle: make shape [
draw: func [] [
shape/draw self
]
]
Here we've made functions that take zero arguments by calling the base class function with the appropriate "self"
Beware: like other words, it gets bound...and the binding sticks. This can get tricky once you start working with abstractions...
selfWordAlias: func [] [
return 'self
]
triangle: make shape [
draw: func [] [
shape/draw get selfWordAlias
]
]
Calling triangle/draw will probably surprise you. You're in the object method and selfWordAlias returns the word "self". But the notion of self was captured and bound at the time the selfWordAlias was defined, which was in the global system context. So that's what you get back.
There are tools for dealing with this, but make sure you've got a firm grip on Scoping in Rebol !
I would have thought that using Rebol's prototypical inheritance is all that you need. It is simpler and more elegant:
shape: make object!
x: 0
y: 0
draw: func [] [probe self]
]
circle: make shape [
radius: 10
]
triangle: make shape []
Which give the following results:
>> shape/draw
make object! [
x: 0
y: 0
draw: func [][probe self]
]
>> circle/draw
make object! [
x: 0
y: 0
draw: func [][probe self]
radius: 10
]
>> triangle/draw
make object! [
x: 0
y: 0
draw: func [][probe self]
]
I use this method when I want to use same function (not the copies of function) for all my objects.
Point the function whenever you create an object, even if you copy another object.
>> f: does [print "hello"]
>> o: context [a: 1 b: :f]
>> o/b
hello
>> p: context [a: 2 b: :f]
>> p/b
hello
>> same? get in o 'b get in p 'b
== true ;they are exactly the same function
>> append last second :f " world!" ;change the function
== "hello world!"
>> o/b
hello world!
>> p/b
hello world!
Ofcourse you should take care about the binding issues. Hope this will help.

Dynamically adding words to a context in REBOL

Imagine the following REBOL code:
foo: context [bar: 3]
I now have a context foo in which 'bar is defined. How can I dynamically inject a new word into this context? Is it possible?
I've tried:
set/any in foo 'baz 3
But that doesn't work because the expression in foo 'baz fails because there is no word 'baz defined in the foo context.
I should add that I realize one way to do this is as follows:
foo-prototype: [bar: 3]
foo: context foo-prototype
foo: context head append foo-prototype [baz: 3]
But what if you don't have access to foo's prototype block?
You can achieve the same by using the existing object as a prototype to create a new object.
>> foo: make object! [bar: 3]
>> foo: make foo [baz: 3]
>> probe foo
make object! [
bar: 3
baz: 3
]
There are several ways to work around the fact that in REBOL/2 it's just not posssible to extend object contexts.
Probably you can just use BLOCK!s instead of OBJECT!s:
>> blobject: [foo 1]
== [foo 1]
>> blobject/bar
** Script Error: Invalid path value: bar
** Near: blobject/bar
>> append blobject [bar 2]
== [foo 1 bar 2]
>> blobject/bar: 3
== 3
You can even make 'self working by just appending the object itself:
>> insert blobject reduce ['self blobject]
== [[...] foo 1 bar 2]
>> same? blobject blobject/self
== true
But as you've asked for extending OBJECT!s, you may go for Peter W A Wood's solution to simply clone the object. Just keep in mind that with this approach the resulting clone really is a different thing than the original object.
So, if some word has been set to hold the object prior to cloning/extending, after cloning the object that word will still hold the unextended object:
>> remember: object: make object! [foo: 1]
>> object: make object [bar: 2]
>> same? remember object
== false
>> probe remember
make object! [
foo: 1
]
In case it's essential for you to keep "references" to the object intact, you might want to wrap the object you need to extend in an outer object as in
>> remember: object: make object! [access: make object! [foo: 1]]
>> object/access: make object/access [bar: 2]
>> same? remember object
== true
You can then safley extend the object while keeping, given you only store references to the container.
REBOL/3, btw, will allow adding words to OBJECT!s.
Said in REBOL/Core User Guide:
"Many blocks contain other blocks and strings. When such a block is copied, its
sub-series are not copied. The sub-series are referred to directly and are the same
series data as the original block."