-
Notifications
You must be signed in to change notification settings - Fork 3
The Invariants of Rebol
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.
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">]
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
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.
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).
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.
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.
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.)
if (any-string? value) and (find any-string! intermediate-type) [ assert [reversible-conversion value intermediate-type] ]
if (any-word? value) and (find any-word! intermediate-type) [ assert [reversible-conversion value intermediate-type] ]
if (date? value) and (find any-string! intermediate-type) [ assert [reversible-conversion value intermediate-type] ]
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.
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.
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] ]