Deeper Rebol Techniques

This page is a collection of bits of information about Rebol which is at more advanced level.

Most of this information has been gathered from the Rebol mailing list. I have edited some of these messages. Archives of this list can be found at http://www.rebol.org.

Programming

If-Elseif...

I usually do it this way:

any [
  if condition1 [
    code1
    true ; needed only if code might return false or none
  ]
  if condition2 [
    code2
    true
  ]
  ...
  (default code)
]

It is readable and evaluates the conditions in order; it is probably faster than writing your own, too.

Gabriele Santilli

Passing refinements between functions

"I think I've had to do something hacky though - my reboldoc function has two refinements - /history and /listwords. I need to be able to pass these through to the recursive call but I've no real idea how to do it. I've implemented a quick and dirty method:

either listwords [
    either history [
        reboldoc/listwords/history sourcefile/2
    ][
        reboldoc/listwords sourcefile/2
    ]
][
    either history [
        reboldoc/history sourcefile/2
    ][
        reboldoc sourcefile/2
    ]
]

"Is this how it should be done? If not, how?"

There has been a number of attempts at this and similar. The solutions are a matter of preference.

Some of them are variations on a theme. Here's a sample.

1. Do as you have done.

2. Try out Ladislav's refined function found in highfun.r at www.rebol.org

3.

Create a special refinement on your function taking one argument.

Only use the refinement when you are making a recursive call from the function itself. Pass in an object that has a logic field for each refinement you want to pass. Downside: "none too pretty" argument handling.

4.

Create a special refinement on your function taking one argument and use the refinement (perhaps called "refinements") to pass in a block of set-word logic! pairs, one for each "normal" refinement. The trick now is interpreting them. You could use it as a object specification. Like this

test-func: function [
    arg /test1 /test2 /refinements refines
] [mode] [
    either refinements [
        mode: make object! refines
    ] [
        mode: make object! compose [
            test1: (test1)
            test2: (test2)
        ]
    ]
    if mode/test1 [
        print "test1 success"
    ]
    if mode/test2 [
        print "test2 success"
    ]
]

>> test-func/refinements "a" [test1: true test2: true]
test1 success
test2 success

5.

Use the following function I created after I exploited paths behaviour when they are evaluated. Note: You really don't need the function. It just serves as a reminder that you can use paths (though you do need to get the path to be evaluated at the appropriate moment).

refine-function: function [
    "Refines a function with the specified refinements." [catch]
    'f "The function"
    refinements [any-block!]
][p][
    p: to-path head insert/only head copy refinements f
    :p
]

Here's an example of using it.

test-func: func[ /test-a /test-b ][
    print "test-func"
    if test-a [ print "test-a" ]
    if test-b [ print "test-b" ]
]

>> my-refined-function: refine-function test-func [test-a test-b]
== test-func/test-a/test-b
>> my-refined-function
test-func
test-a
test-b

6.

A combined approach for a recursive function that I developed while trying to answer your post - thus not heavily tested.

recursive-func: function [
    arg [integer!]
    /test-a
    /test-b
    /mode refined-mode [path!]
] [recursive-call refinements] [

    ; Lets set the "mode" it it hasn't been done already (The Overhead)
    ; If you didn't want the overhead, you could do this outside the function.
    if not mode [
        refinements: copy [mode] ; Need mode here for the recursive call later.
        if test-a [insert tail refinements 'test-a]
        if test-b [insert tail refinements 'test-b]
        refined-mode: refine refinements
    ]

    ; Now form the recursive call (playing with paths).
    recursive-call: refine-function recursive-func (to-block :refined-mode)

    ; Now the real logic of this function (with a touch of overhead)
    print "test-func"
    if test-a [ print "test-a" ]
    if test-b [ print "test-b" ]
    if greater? arg 1 [
        recursive-call (subtract arg 1) (:refined-mode)
    ]
]

While it looks heavy, I think it is worth with especially if you have over two refinements. It don't know how it will fair in performance terms - it would be interesting to see. However, my main use for this will be to aid readability of my scripts.

____ ...

No doubt there would be other schemes. I have a feeling that this was going to get attention in the language at some point - but I could be wrong.

Brett Handley

Using code blocks and bind

> block: ["a" "b" "c"] > code: [print member] > foreach member block [do code] > > Upon execution, I get : > > ** Script Error: member has no value. > ** Where: print member

The problem is that the word 'member inside the block referenced by 'code is bound to the global context when the script is loaded, and it remains so when the "do code" is executed inside the foreach loop. However,

foreach member block [...]

creates a local variable, also called 'member which will be set (bound) successively to the items contained in 'block. The name of the iterator variable is irrelevant to the problem. You will get the same problem if you say

foreach x block [...]

because the local variable 'member is never used in the foreach body block.

You can use the function 'bind to bind the words in the external (to the function foreach) 'code block to the local context of the foreach function by binding the block to the local variable 'member.

>> block: ["a" "b" "c"]
== ["a" "b" "c"]
>> code: [print member]  ;context for member made here, value given later
== [print member]
>> member: 5
== 5
>> foreach member block [do code]
5
5            ;member in code bound to global context
5
>> foreach member block [do bind code 'member]
a
b            ;member in code bound to local var member
c

The latter gives the same result as moving the 'code definition inside the foreach loop.

>> foreach member block [code: [print member] do code]
a
b
c

Some caution is necessary when using advanced functions like bind. For instance, in a more general case than yours, there might be some words in the external block that need to retain the values of their global binding despite the fact that those words are also used locally in the calling function and others that need to be rebound to the calling function's local context. In such a case, using bind as above will not give the desired effect.

The thing to remember is that words have values only within a context, and the same word may have different values in different contexts.

[joined emails here - Brett]

I showed you how to bind the words in the code block to the local variable 'member

>> block: ["a" "b" "c"]
>> code: [print member]
>> foreach member block [do bind code 'member]
a
b            ;member in code bound to local var member
c

What I failed to mention is that the use of bind is permanent (until some other 'bind occurs) so that the after the foreach loop the global block referenced by 'code remains bound to the local context of foreach.

>> get second code
== "c"

This is a side effect of using 'bind as above, and may not be the desired effect. It can also lead to a crash when accessing the value of the code block if there has been a 'recycle or garbage collection which destroys the local context of the 'foreach function. The easy way to leave the original code block unchanged is to copy before the bind.

>> code: [print member]
>> get second code
** Script Error: member has no value.
** Where: get second code

So 'member is bound to the global context but has no value.

>> foreach member block [do bind copy code 'member]
a
b        ;member in COPY of code bound to local var 'member
c
>> get second code
** Script Error: member has no value.
** Where: get second code

The global code block remains unchanged.

In general, it is probably better, when possible, to define code blocks in the context in which they will be used, as there are further complications with nested sub-blocks as well as in figuring out how to do the necessary binding when the functions, function calls, and code blocks are complex. In addition, when modules are added to REBOL some of our current code relying on bind may be broken.

Larry Palmiter

Using blocks defined in functions

[This post relates to the behaviour shown in section title "Words are not variables" found on the following page:

Tips and Techniques

- Brett]

Think of the advantages that this trick can give you. This kind of assignment can be useful for implementing what the C world calls static local variables.

You can set a word to a literal string value, then append to that string to create a string accumulator. This allows you to build a string incrementally.

You can set a word to a literal block and store values in it. This allows you to use a function like a Scheme closure, a function with values bundled in it, like OOP in reverse. This technique allows OOP-like programming with better control of your data because it is hidden inside the function. Look at http://www.bigfoot.com/~brian.hawley/rebol/require.r for an example of how this technique can make for bulletproof code.

The most fun with this technique comes when you use compose to create your code blocks. For example, consider this:

f: func [key] [
    table: make hash! [a "a" b "b"] table/:key
]

Trivial, true, but imagine that pattern with a much larger hash table, or a large binary value, or a dozen charsets for a parse process. You can't directly represent those values as literals - they get recreated every time. Do that in a function and the function gets really slow. But do this:

f: func [key] compose [
    table: (make hash! [a "a" b "b"]) table/:key
]

and the hash table is only created once, right before the function is created. All calls to f then reference the now literal hash table, making for a very fast, memory efficient function.

Brian Hawley

Dialects

Interpreting Dialects

When thinking about dialects, I thought of two ways to intrepret information in those dialects.

1) I get the information and (bind? and) DO it assuming I've previously set the words up with useful functions and values. Advantage of this, ease of implementation maybe, since I rely on the information creator to get the protocol/grammar right. Disadvantage of this, if I don't trust them they might do something with all that expressive power I've just ceded to them.

2) I parse the information according to a grammar and act accordingly. The creator of the information is limited to the grammar. Advantage. Safer for untrusted/important/sensitive content. And I can see if my assumptions were wrong about the content (the parse returned false). Disadvantage. A bit more coding, because I don't assume the grammar has been followed properly.

Brett Handley

Sophisticated Techniques

File Locking

>Anyone have any experience with this? > >Need to lock some files which can be accessed by multiple users at once, >need to flock to maintain data integrity.

unfortunatly rebol does not have built-in support for file locking. However, I've found a couple of methods that seem to work: the easiest is to use functions like this

try-get-filelock: func [ file ] [
    not error? try [ make-dir rejoin [ %file-lock- file "/" ] ]
]

free-filelock: func [ file ] [
   delete rejoin [ %file-lock- file "/" ]
   return
]

get-filelock: func [ file retries /local retry ] [
   retry: 0
   while [not try-get-filelock file] [
      wait 0.5
      if (retry: retry + 1) > retries [ return false ]
      ]
   return true
   ]

and obtain the lock with a line something like this:

if not get-filelock %msgboard.txt 40 [ print "---error---" quit ]

possibly with extra code to email you notification when locking fails (in case an unattended script crashed without unlocking things).

another alternative is to set up a server to handle requests for locks, and have the program query the server to lock files. an example of how to do this is provided below if you want to try that approach.

REBOL [
   Title:      "REBOL Locking System"
   Date:       23-June-1999
   File:       %locker.r
   Author:     "Cal Dixon"
   Email:      deadzaphod@hotmail.com
   Version:    1.1
   Purpose:    "To provide functions for voluntary resource locking in rebol"
   Rights:     {
               Copyright (c) 1999 Caleb Dixon.  This version is free for ANY use.
               Do whatever you want with it as long as you don't claim to have created this.
               }
   Note:       {
               Be sure to run the 'lock-server function in a separate rebol process before
               calling the other functions, they will fail if the server is not available.
               Once the server is running, you can just "do %locker.r" then use 'get-lock and
               'free-lock in any script that needs resource locking.
               }
   Comment:    "This version does not do enough error checking.  This will be fixed later."
]

; change this line if you want to use a port other than 7007 for this service.
if not value? 'rebol-lock-port   [rebol-lock-port: 7007]

lock-server: function [ {Handles requests to lock and unlock named resources.} ] [] [
   locks: make block! []
   version: 1
   listener: open/lines join tcp://: rebol-lock-port

   while [true] [
      while [true] [
         conn: first listener
         wait conn
         if error? try [ req: load first conn ] [ req: "ERROR" ]
         if all [ (block? req) (>= (length? req) 2 ) ] [ break ]
         close conn
         ]
      probe req
      if (= to-lit-word (pick req 1) 'lock) [
         if none? find locks (pick req 2) [ append locks reduce [ (pick req 2) true ] ]
         if (available: do rejoin [ "locks/" (pick req 2) ]) [
            do rejoin [ "locks/" (pick req 2) ": false" ]
            ]
         insert conn rejoin [ "[" available "]" ]
         ]
      if (= to-lit-word (pick req 1) 'free) [
         do rejoin [ "locks/" (pick req 2) ": true" ]
         insert conn "[ true ]"
         ]
      if (= to-lit-word (pick req 1) 'version) [
         insert conn rejoin [ "[ " (>= version (pick req 2)) " ]" ]
         ]
      close conn
      ] ;end while loop
   ] ;end lock-server function


try-obtain-lock: function [ "Attempt to lock a named resource"
whichword [word!] /server "Use specific lockserver instead of localhost:7007"
servname [url!] "Address of lockserver" ] [ conn r ] [

   either server [
      conn: open/lines servname
      ] [
      conn: open/lines join tcp://localhost: rebol-lock-port
      ]
   insert conn rejoin [ "[lock " whichword "]" ]
   r: do load first conn
   close conn
   return r
   ]

get-lock: function [ "Attempt to lock a named resource, and retry if it is not available"
whichword [word!] retries [integer!]
/server "Use specific lockserver instead of localhost:7007"
servname [url!] "Address of lockserver" ] [ gotit ] [

   either server [
      while [ not (gotit: try-obtain-lock/server whichword servname) ] [
         if (retries < 1) [ return gotit ]
         retries: retries - 1
         wait 1
         ]
      ] [
      while [ not (gotit: try-obtain-lock whichword) ] [
         if (retries < 1) [ return gotit ]
         retries: retries - 1
         wait 1
         ]
      ]
   gotit
   ]

free-lock: function [ "Free a named resource" whichword [word!]
/server "Use specific lockserver instead of localhost:7007"
servname [url!] "Address of lockserver" ] [ conn r ] [
   either server [
      conn: open/lines servname
      ] [
      conn: open/lines join tcp://localhost: rebol-lock-port
      ]
   insert conn rejoin [ "[free " whichword "]" ]
   r: do load first conn
   close conn
   return r
   ]

check-lock-server: function [ "Check for the presence of a usable lock-server"
/server "Use specific lockserver instead of localhost:7007"
servname [url!] "Address of lockserver"] [ conn versionok ] [
   either server [
      if error? try [ conn: open/lines servname ] [ return false ]
      ] [
      if error? try [ conn: open/lines join tcp://localhost: rebol-lock-port 
] [ return false ]
      ]
   insert conn "[version 1]"
   if error? try [ versionok: do load first conn ] [ return false ]
   close conn
   return versionok
   ]

Cal Dixon

Idioms

There's a certain zen to REBOL and it takes time to understand-- something that I am always learning.

REBOL has a lot of what might seem like idioms, which really are ways to do things that make life a lot easier. They stem from the craftsmanship in REBOL, the rather lengthy effort that went into its design, implementation and polish. Many problems you face in programming have a nicely crafted solution sitting inside this little binary interpreter, just waiting for you to discover it when you need it.

The code that comes from REBOL should demonstrate that Zen because we all are lucky in that we can pick Carl's brains over the challenges we encounter. Also, we're lucky because we get to write REBOL code as an occupation, so we get to find all those little crafted edges inside. I've always hoped to pass on what little I have learned of the Zen of REBOL to any who would walk in that path.

Here is an example of some REBOL Zen style idioms.

Consider a block of similar objects:

block:  [make object! [name: "foo" phone: #222-1111 ... ] ... ]

Now you're writing a function and you have this block and it is really long with many of the same kinds of objects, but you need to see if there is an object which has the name field set to "Waldo" and you need to see if the waldo object's phone field is set to none, If you don't find this object you want to do somehing. If you do find waldo and waldo has a phone you want to call waldo, otherwise you want to complain that he doesn't have a phone.

Some people might code it like this:

find-waldo: func [block [block!] /local found waldo no-fone?][
    found: false
    foreach obj block [
        if obj/name = "waldo" [
            waldo: obj
            found: true
            no-fone?: not none? obj/phone
        ]
    ]
    if not found [wheres-waldo?]
    either no-fone? [waldo-has-no-phone][call-waldo waldo/phone]
]

That's a fair approach, similar to how you might tackle the problem in basic, maybe. But with REBOL you can get much more done in place. Most things return meaningful values so the left side of most functions represent an excellent place to dock another useful function, save space, save steps, and preserve the utility of results.

How about this:

find-waldo: func [block [block!] /local result][
   if not result: foreach obj block [
       if obj/name = "waldo" [break/return any [obj/phone yes]]
   ][wheres-waldo?]
   either issue? result [call-waldo result][waldo-has-no-phone]
]

Okay, so we have less local variables, the code is smaller and therefore is more efficient usually (and in this case definitely). The first example trudged through the whole block before deciding the outcome, where above we BREAK/return as soon as we find waldo. The FOREACH will return the last evaluation, so if FOREACH makes it through the whole block with out ever finding waldo it will return a NONE from the last evalutation of the IF statement. We use the result of FOREACH to immediately determine if we found waldo. Now if result is not a NONE, it will be either an issue!, waldo's phone number (#222-111-3333), or a TRUE value (yes). The TRUE is arbitrary since we just have to return a non false / non none from BREAK/return and we only check if we have an issue which means it must be Waldo's phone number.

Programmers will have different styles, but the language also has a style of its own.

With REBOL it usually comes down to taking a function, writing it, then rewriting it a few more times, carving out the fat while expanding the capability. As a general rule most REBOL functions that are written can be reworked to accomplish more, to provide more use in the same amount of space or less. Code in the eye of the fly and seek to know the true path of the REBOL way! :-)

jeff

Performance

> is it possible to get complexity, memory structure, and performances, of > rebol's elements, and more particularly rebol's list and blocks data types > and algorithms ?

It should be pretty much self-explanatory, from the datatype. Blocks are basically arrays. Hashes are arrays with a hash table for all entries, making 'find operations quicker, and lists are linked lists.

Complexities are as you would expect them, e.g. 'find is O(n) in a block! or list!, and anywhere from O(1) to O(n) (O(n) worst case) in a hash!, depending on the effectiveness of hashing. O(1) is typical though. Insertion and deletion are O(1) at the tail of any data structure, O(n) anywhere else, except for list!, where they are O(1) everywhere.

Hashes are useful as a replacement for blocks when keyed lookups are needed. Lists are useful for queues or stacks. Lists should be used carefully though: generally you should only use insertion, deletion and back,next,skip. Many other operations, even index?, are O(n), and thus negate the advantage of using a list!.

Holger Kruse