Transitioning from REBOL 2 to REBOL 3

Introduction

This page is to list the things I need to learn to make the transition to REBOL 3. I'll update it as I go along by adding, updating and deleteting. Of course it will be an incomplete list as it is based upon my experiences.

REBOL 3:

Porting guide on Trello

There's a Rebol Porting guide available now:

Rebol 3 Porting Guide (Ren/C Branch)

At time of writing documents transition from Rebol 2 to Rebol 3 and Rebol 3 to Ren/C.

append object!

R3: Objects can now be extended using Append

>> o: make object! [a: 1]
== make object! [
    a: 1
]
>> append o [b: 2]
== make object! [
    a: 1
    b: 2
]

In addition the Extend function can be used to make this behaviour obvious in your code:

>> o: make object! [a: 1]
== make object! [
    a: 1
]
>> extend o 'b 2
== 2
>> ?? o
o: make object! [
    a: 1
    b: 2
]

bind

This is fine in Rebol 2, but fails with an error in Rebol 3 if x is not a word of the object:

bind 'x object

But the following works as expected:

bind [x] object

call

Call is in a process of being improved.

Initially there was no redirection support (like Rebol 2 has) so I created a workaround which leveraged Rebol 2.

There is still no interactive console program support. My workaround that leverages Rebol 2 supports this. See call-server.r:

get, set

Rebol3: Get/Set can now take a path! or object!

>> o: context [a: 1 b: 2]
== make object! [
    a: 1
    b: 2
]
>> get o
== [1 2]

>> set o [3 4]
== [3 4]
>> o
== make object! [
    a: 3
    b: 4
]

>> p: 'o/a
== o/a
>> get p
== 3

collect-words

R2, R3: Collect unique words used in a block.

delete/any

Delete /any refinement is not supported at the time of writing.

disarm

Disarm does not exist in Rebol 3, errors behave differently to Rebol 2 (are not hot). They have to be "armed":

do make error! {my error}

do/next

Getting the result and next position has changed.

Rebol 2:

>> do/next [1 2]
== [1 [2]]

Rebol 3:

>> do/next [1 2] 'position
== 1
>> ?? position
position: [2]
== [2]

To make Rebol 3 equivalent to Rebol 2 use something like:

>> reduce [do/next [1 2] 'position position]
== [1 [2]]

foreach

Foreach gets some new tricks in Rebol 3...

R3: Foreach can take a set-word! type variable to get a reference to the series.

>> foreach [w s:] [a 1 b 2] [?? w ?? s]
w: a
s: [1 b 2]
w: 1
s: [b 2]
w: b
s: [2]
w: 2
s: []
== []

R3: Foreach can process an object:

o: make object! [a: 1 b: 2]
>> foreach w o [print mold reduce [:w get w]]
[a 1]
[b 2]
>> foreach [w v] o [print mold reduce [:w v]]
[a 1]
[b 2]

funct

Funct may be a useful way to avoid having to use Context. It saves having to use /local when definining functions.

It has an extern refinement to declare set-words that should not be made local. A downside of /extern is that non-local words to the function are located at the end of the function body making it them easy to overlook.

Presumbably FUNCT was introduced to make coding safer by not accidently introducing variables in an outer context. In practice, the number of errors of omission I make with FUNCT is about the same as with FUNC, but sometimes frustratingly harder to track down. Perhaps it's just me...

load

load/next

Load no longer has a /next refinement, but Transcode does.

>> load/next {now}
LOAD/next removed. Use TRANSCODE.
** script error: load has no refinement called next

But transcode only accepts binary, so convert to string first:

>> transcode/next to binary! {now}
== [now #{}]

Chris Ross-Gill has created a function based on transode which is equivalent to load/next in Rebol 2:

load-next: funct [
    {Load the next value. Return block with value and new position.}
    string [string!]
][
    out: transcode/next to binary! string
    out/2: skip string subtract length? string length? to string! out/2
    out
] ; by Chris Ross-Gill.

load/markup

Load has lost it's /markup refinement.

Rebol 2:

load/markup "string"

Rebol 3:

decode 'markup #{...binary...}

make function!

R2: f: make function! [a] [print a]

R3: f: make function! [[a] [print a]]

See: Make Function! Must Change for the reasons.

needs (in script header)

Rebol 2: The Needs field of a script header did not check for a block of referenced scripts.

Rebol 3: The Needs field will import referenced scripts as modules. This is not the same as DOing the script.

See: How does R3 use the Needs field of a script header? Is there any impact on namespaces?

parse

A number of changes have been made to Parse so I have created a page for these: Differences in Parse from Rebol 2 to Rebol 3.

path!

paths for blocks

Rebol 3: Paths can now be used to select/change values in a block where the key is a set-word!. See A change to path for blocks.

>> s: [x 0]
== [x 0]
>> s/x: 9
== 9
>> s
== [x 9]

Indexing by integer

Rebol 2: x/1 is the first element, x/-1 is the prior element, x/0 is always none.

Rebol 3: x/1 is the first element, x/-1 is the element before the prior element, x/0 is the prior element.

Less active paths or path bugs

Paths seems less active than in Rebol 2, for example given this:

fn: func [/test][?? test]
p: 'fn\test

Rebol 2:

>> do :p
test: true
== true

Rebol 3:

>> do :p
== fn/test

>> do reduce [:p]
test: true
== true

This could be a bug see DO evaluation of path! and lit-path! not consistent with word! and lit-word!

Also, unfortunately, there could a bug with single segment paths.

>> p: to path! 'block
== block
>> do reduce [:p]
** Script error: cannot access end! in path block
** Where: do
** Near: do reduce [:p]
>> get p
** Script error: cannot access end! in path block
** Where: get
** Near: get p

query

Rebol 2: query object - retrieves modified words, query/clear object - reset modification flag

Rebol 3: Unsupported. (why?!...)

quote

>> equal? first [:some/path] quote :some/path
== true

read

Ashley said:

remove-each

Rebol 2: Returned the modified series.

Rebol 3: Returns the number of elements removed.

So one liners may have to become two lines eg:

new-series: append remove-each x [not set-word? :x] copy old-series 'none

will need to be changed to the less concise:

remove-each x [not set-word? :x] new-series: copy old-series
append new-series 'none

rename

Rebol 2: Rename expected a simple file name target, not a path.

Rebol 3: Rename expects a fully qualified target.

repeat

For series values the loop variable is set...

Rebol 2: to each element of the series (like foreach)

>> repeat x [a b c][?? x]
x: a
x: b
x: c
== c

Rebol 3: to the indexed series (position)

>> repeat x [a b c][?? x]
x: [a b c]
x: [b c]
x: [c]
== [c]

return

To return an unset! use Exit

Rebol 2:

f: func [][return] f

Rebol 2, Rebol 3:

f: func [][exit] f

shift

Rebol2:

shift/logical -306674912 1 
== 1994146192

Rebol3:

shift/logical -306674912 -1
== 9223372036701438352

take

Rebol 2, Rebol 3: Take removes and returns one or more elements from a series.

to-local-file

Rebol 2: Slash omitted.

>> to-local-file %folder/
== "folder"

Rebol 3: Platform specific slash.

>> to-local-file %folder/
== "folder\"

url!

Text is automatically urlencoded when added to an url!.

url! needs more thought as it is too aggressive.

use

Variables are initialised to none! in Rebol 3. So if your Rebol 2 code relied on use variables being unset! you'll need to change the logic.

Rebol 2:

>> use [x][?? x]
** Script Error: x has no value
** Where: ??
** Near: mold name: get name

Rebol 3:

>> use [x] [?? x]
x: none
== none

words-of object, functions...

Rebol 2:

exclude first object [self]

Rebol 2, Rebol 3:

words-of object

Rebol 3 but not Rebol 2

map!

The MAP! datatype is a dictionary, providing hashed access. Map! supports word! string! and integer! as keys. Create a map! with the Map function.

m: map [a 1 b 2]
m/c: 3 ; Add new key/value pair.
>> m
== make map! [
    a 1
    b 2
    c 3
]
>> m/c: none ; Remove key/value pair.
== none
>> m
== make map! [
    a 1
    b 2
]

May be still being reviewed - ask on the stack overflow chat room.

Closure functions

typeset!

R3: A typeset! defines a set of data types.

tmp-type: make typeset! [time! money! percent!]
f: func [arg1 [tmp-type]] [?? arg1]
>> f 3
** Script error: f does not allow integer! for its arg1 argument

Rebol 2 but not Rebol 3

list! hash!

Other Behaviour changes

Script Headers

Script header appears to be optional.

Line Ending Conversion

R2: Write converts line ending to platform.

R3: Write does no line ending conversion. See Enline and Deline.

Need to look into how line endings are encoded in Rebol 3.

HTTP headers, GET, POST, PUT, DELETE.

Rebol 2: You use read/custom

Rebol 3: You use read for GET. You can WRITE for GET, PUT, POST, DELETE with headers.

WRITE can take a block for the data argument.

Block arguments to write are as follows, where each item is optional:

[
    method ; word!, if not specified = POST
    target ; file! or url! - overrides any path given in url
    headers ; block! e.g. [Content-Type: "text/plain"]
    content ; any-string! or binary!
]

For example a POST request could be written as:

write url [
    [Content-Type: "text/plain"] {My data.}
]

More doc

See this pull request for some documented differences https://github.com/red/red/pull/421.

Contexts, Binding, Modules, Evaluation

Things to look into

New stuff

Networking

Changes, Bugs, Source

user.r, rebol.r

What configuration files are there for Rebol R3 and how are they loaded?

Rebol 3 Examples

Rebol 3 Extensions

ODBC Extension

Oddities

Save

@GrahamChiu found this:

>> save/all %test.txt [ 1 2 3 ]
** Script error: encode does not allow block! for its data argument
** Where: if save
** Near: if lib/all [
    not header
    any [file? where url? where]...

whereas, this is fine

save/all %test.r [ 1 2 3 ]

See: Curecode ticket #1937

Documentation bugs

data: read port
wait port

Tests for problems

Each item below is an expected feature but is currently a problem, use the test below to determine if the problem is resolved (true) in your version of Rebol.

body-of should maintain context from returned result.

use [x] [x: 1 f: func [][x]]
value? first body-of :f

See: body-of strips context from returned result

find/any should implement wildcards

"aXb" = find/any "aXb" "a?b"

sort/skip/all should compare all fields

[a 1 a 2] = sort/skip/all [a 2 a 1] 2

A workaround by Ashley is:

sort-all: funct [
    series [series!]
    size [integer!]
] either [a 1 a 0] = sort/skip/all [a 1 a 0] 2 [[
        blk: make block! rows: divide length? series size
        repeat i rows [append/only blk copy/part skip series i - 1 * size size]
        clear series
        foreach row sort blk [append series row]
        also series blk: none
    ]] [[
        sort/skip/all series size
    ]]