Skip to content

The Invariants of Rebol

Christopher Ross-Gill edited this page Aug 1, 2016 · 1 revision

Building a mental model of a language is easiest if there are rules you can use for grounding. It is useful to know what are rules, and what aren't rules.

Table of Contents

TO conversions

Rebol uses the TO function in order to convert one type to another. Its first parameter is the type you want to convert to, and the second parameter is the source value.

>> to integer! "42"
== 42

>> to date! "1-Jan-2010"  
== 1-Jan-2010

>> to block! <a href="http://rebol.com">
== [<a href="http://rebol.com">]

There are convenience functions for each type which are a hyphenation of the word TO and the type without its exclamation mark:

>> to-integer "42"
== 42

>> to-date "1-Jan-2010"  
== 1-Jan-2010

>> to-block <a href="http://rebol.com">
== [<a href="http://rebol.com">]

Success and Failure

Not all TO conversions will succeed

Some target types are too narrow to express an arbitrary value of the source type, which means conversions will not always succeed:

>> to-integer "Hello"      
** Script error: cannot MAKE/TO integer! from: "Hello"
** Where: to to-integer
** Near: to integer! :value

>> to-date "99-Apr-9999" 
** Script error: cannot MAKE/TO date! from: "99-Apr-9999"
** Where: to to-date
** Near: to date! :value

One-Way Conversions

The ability to successfully convert from one type to another does not mean that converting back will give the original input. Here are some examples.

STRING! via DATE!

A wide variety of input formats are supported for dates, yet there is only one default output format:

>> to-string to-date "1-1-2009"
== "1-Jan-2009"

This default is the ISO standard covering the exchange of date and time-related data (ISO-8601).

BLOCK! via STRING!

In the first step of the conversion (from block to string), the words in the block are converted to strings using FORM and then the strings are concatenated together. But there are no spaces between the words of the block - the spaces that appear to be there are really just artifacts of Rebol syntax, not really there once the value is loaded. So the resulting string is "abc".

>> to-string [a b c]
== "abc"

Then TO-BLOCK of that string effectively treats the string as Rebol syntax - a special case of TO-BLOCK for STRING! and BINARY! input.

>> to-block to-string [a b c]
== [abc]

This results in the word 'abc in the block.

Other Types via BLOCK!

TO-BLOCK of a value other than the special cases of STRING! and BINARY! simply wraps the value in a block. Let's see this with content a href="http://rebol.com" (the angle brackets are part of the syntax, but not actually in the series data):

>> to-block <a href="http://rebol.com">
== [<a href="http://rebol.com">]

TO-TAG treats that block like TO-STRING does. The reason is that they use the same code, which is to FORM the contents of the block and then concatenate them.

>> to-tag to-block <a href="http://rebol.com">
== <<a href="http://rebol.com">>

Our result is a TAG! whose contents literally have a set of angle brackets inside of it: <a href="http://rebol.com">. The syntax of the tag! type then displays that value with another pair of angle brackets around it.

Reversible Conversions

In the following sections, we define a reversible conversion of a value by way of an intermediate-type in the following way:

reversible-conversion: func [value intermediate-type /weak /local value-type result] [
    value-type: type? value
    result: TO value-type TO intermediate-type value
    either weak [
        if any-word? result [
            assert [none = bind? result]
        ]
        return value = result
    ] [
        return value == result
    ]
]

For the default, we use the == (aka STRICT-EQUAL?) operator so that case sensitivity and binding are taken into account. If we use the /weak refinement then we use the = (a.k.a. EQUALS?) operator, which is more lax:

>> "AbCdE" = "ABCDE"
== true

(You can read more about the equality heirarchy in the Comparisons article.)

ANY-STRING! via ANY-STRING!

if (any-string? value) and (find any-string! intermediate-type) [
    assert [reversible-conversion value intermediate-type]
]

ANY-WORD! via ANY-WORD!

if (any-word? value) and (find any-word! intermediate-type) [
    assert [reversible-conversion value intermediate-type]
]

DATE! via ANY-STRING!

if (date? value) and (find any-string! intermediate-type) [
    assert [reversible-conversion value intermediate-type]
]

ANY-WORD! *weak* via ANY-STRING!

Both words and strings contain character data, but there are some strings that aren't legal words. Words cannot have spaces or colons in them, for example. Yet converting from an ANY-WORD! into an ANY-STRING! and back preserves the spelling of the word in a weak sense:

if (any-word? value) and (find any-string! intermediate-type) [
    assert [reversible-conversion/weak value intermediate-type]
]

Do note that for most uses of words, one of their most important attributes is their binding. This conversion loses that.

Reflection

It is important to bear in mind that the structures which can be specified through Rebol's source notation are a subset of what can be constructed at runtime. To see an example of this, note that executing the following Rebol code builds a structure with an aliased block:

>> foo: [a]
== [a]

>> bar: reduce [foo foo]
== [[a] [a]]

>> append first bar 'b
== [a b]

>> probe bar
== [[a b] [a b]]

Had you simply defined bar in source as a] [a it would lead to an object that looks like it has the same structure, but it behaves differently:

>> bar: [[a] [a]]
== [[a] [a]]

>> append first bar 'b
== [a b]

>> probe bar
== [[a b] [a]]

There tend to be limits to what can be specified as literal declarative values, and Rebol is not a declarative language. The native way of specifying runtime values is through specifying the procedural code that should be run to construct them in memory.

Some other examples of information that isn't possible to concretize as source include binding information, series positions, and object references.

a string that can LOAD will SAVE back to a string that will LOAD to an equivalent structure

The following is true for any string with no serialized syntax in it:

assert [string? str]

wasValidRebol: try/except [
    structure: load str
    true
] [
   ; not all strings will successfully LOAD
   false
]

if wasValidRebol [
    strFromStructure: copy ""
    save strFromStructure value

    ; uncertain if this is true...?
    comment [
         assert [str = trim/lines strFromStructure] 
    ]

    ; but this is, correct?
    assert [structure = load strFromStructure]
]
Clone this wiki locally