The Unofficial Meta Documents: Improve your Meta code

When you start programming in Meta, chances are that you already have some programming experience.
It might even be that you have previous experience in the family of Rebol related languages. Even better. But with or without previous programming experience, when you start out and try to get your ideas translated into beautiful code, coding can get pretty stubborn and in that case you will probably focus on how to get to the result: A compiling program, a working program producing the outcome that you had expected. How you get things to work, there are a lot of ways that will work. Even in Meta, and even in the current state of development this language is in now (Spring 2023) this is possible. But there are many occasions where this working Meta code can be replaced with what you could call an improved programming style for Meta.

The advantages of many of the examples you will encounter on this page will often be that the generated code will be more efficient. Some examples will open up your eyes to the other available methods of Meta. Others will be more pleasing to the eye or improve readability of the code.
Anyway, here is a little guide to help you on your way and improve your Meta code.

Start a Meta script

The first line of the script, or the header is

Meta []

Within the square brackets you can put any additional information about the program preferrably in Meta style.

Variable names

Meta is not case-sensitive, so that is a good thing if you are like me and you have experienced misshifting on a CaMelCaSe variable name and your IDE could put red curly lines everywhere in your code but could not help and suggest you were missing or used an extra capital letter.

Off course you could use capital letters for a variable to signal that this variable is meant to represent a constant value. But this is not a convention by default. Meta could not care less about you using capitals or not. Constant values are usually declared using an '='-sign, this opposed to the colon that is used in regular assignments. Using the equality sign can trigger compilation errors when you try to reassign a different value to your 'constant'

SLOGAN= "We love this Meta programming language"
TAU= 6.28318530717958647692528676655900576839433879875021

The slogan defined above can also be used as Slogan or slogan, CAPS just do not matter. Because of the floating point representation, Tau will also be 6.28318530717958623.

Good names versus not so good names

Number one reason to use good descriptive names as your variable names is you. You, at least you, need to read your own code and rebuild your understanding of whatever it was when you created your script long or not so long ago.

A good name is descriptive of what it is used for. Because Meta is compiled a longer name shall not impact the performance in any way. With an interpreted language, the length of your variable names could have a noticable effect.

So choose descriptive names. And with variables representing a LOGIC! value (True or False) preferrably a questionmark is added to the name. Say for a value that has gone past the maximum allowed value that could be tested with

if value-too-big? [write/line "That number is way too large for me!"]

And the not so good names? We leave that up to you, the reader ;-)

But beware of how to name your variables!

When naming your variable take care of not picking one of the predefined dictionary items. Your definition will override this earlier definition and possibly cause trouble that way.

I made such a (beginner) mistake recently, nobody is perfect, when I used COUNT for a counter.

count: 0
...
increment count
...

And completely forgot about COUNT returning the length of a string! So where I also used that, its definition had been altered and cost me several hours to figure it out.

So you are warned.

File I/O

One of the design points of REBOL was to make files look like series. Unfortunately, the implementation is not entirely complete, so most REBOL programs use the special functions READ and WRITE to handle file operations.

Meta has a better series abstraction for files, so you should use regular series methods on PORT!s

Use OPEN/NEW or OPEN/APPEND to open a text file PORT! for writing, finalise the file with CLOSE.

Use APPEND to write to the PORT!.

You can read from a PORT! with TAKE or TAKE/LINE

Meta has a different lexical notation for file paths than REBOL. It doesn't use the leading % so you can use the regular notation for files, as long as you use one of the regular prefixes:

./file/from/working/folder
~/file/from/home/folder
/file-in-root
/. ; Root folder

You can take a look at the example section to find an elaborate example of handling files.

Addition of 1

When you want to build a loop construct you often need to add 1 to the index you are using to go through you data. The next code is how you might do that.

Regular Programming Code:

value: value + 1

improved Meta Code:

INCREMENT value

This will generate more efficient code. It is like the C command value++.

Subtraction of 1

Like the addition, subtraction has its own improved code.

Regular Programming Code:

value: value - 1

improved Meta Code:

DECREMENT value

This will generate more efficient code. It is like the C command value--.

The Rebol order of logic in conditional statements

A Rebol programmer probably likes this naive way of coding this better.

Regular Programming Code:

if 0 = value []

improved Meta Code:

if value = 0 []

The order of this expression is often reversed in Rebol-like languages because of the way expressions are evaluated. The reverse order prevents the need for using parenthesis. Meta does not need this, so expressions for evaluation can be stated in the form we humans are more familiar with. But sometimes the first method really saves us from the need to group parts of the expression.

Loops in Meta

REPEAT a simple loop

REPEAT 10 [
    WRITE/LINE "Hello World!"
]

FOR loop

for index 10 [
    write "index = " write/line index
]

From 5 up to and including 10.

for index [5 10][
    write "index = " write/line index
]

WHILE loop

result: 1
n: 5
WHILE n > 1 [
    result: result * n
    decrement n
]

UNTIL loop

result: 1
n: 5
until (
    result: result * n
    decrement n
    n <= 1
) []

Or like this

result: 1
n: 5
until (
    n <= 1
) [
    result: result * n
    decrement n
]
write/line result

How to name a logic value in Meta

Boolean values are called LOGIC! in Rebol like languages. Also in Meta. Remember the LOGIC!

Variable names that express a logic value are best named with an added sigil or questionmark. Thus for example same? long-enough? payed? etc.

is-free?: True
if is-free? [write/line "Yes it is free!"]

Example with multiple improvements

If you want to go through a string! character for character, you could take advantage of the ADVANCE method.

See how this code can be improved:

word: "51a234"
numeric?: true
while all [0 < count word
           numeric?][
    digit: copy cut word 1
    any [digit = "0" digit = "1" digit = "2" digit = "3" digit = "4"
         digit = "5" digit = "6" digit = "7" digit = "8" digit = "9"  numeric?: false]
    word: skip word 1
]
write "Word: " write word write " is " unless numeric? [write "not "] write/line "numeric!"

Regular Programming Code:

word: skip word 1

improved Meta Code:

advance word

and

Regular Programming Code:

digit: copy cut word 1
any [digit = "0" digit = "1" digit = "2"
     digit = "3" digit = "4" digit = "5"
     digit = "6" digit = "7" digit = "8"
     digit = "9"  numeric?: false]

improved Meta Code:

character: copy cut word 1
digit?: find "0123456789" character

Efficiently fill values in an array

Regular Programming Code:

Let [board] binary! 50

For index [1 (count board)][
	poke board index 0
]

improved Meta Code:

Let [board] binary! 50

change/repeat board 0 50

Troubleshooting

Errors of different types.

You will for sure meet some of your new friends in the future.

Compile errors Meta

This next error will be a regular guest

Compiling program file scripts/svg/math-diagram.meta
** Script hint: method "join/with" does not work on types combination:
Returned type: fixed-string!
Input types:
1: fixed-string! [join/with
    join/wi...
2: integer! [
    svg-stroke-width...
At: [join/with join/with

What happened here is while you can WRITE numerical values to the console, JOIN them to STRING! types is not supported. You must 'cast' them to STRING! type yourself.

The erroneous code is shown here on the left, the correct right.

Not working Programming Code:

number: 128
string: "My value is: "
string: join/with string number

improved Meta Code:

number: 128
string: "My value is: "
string: join/with string TO STRING! number

Strange results

Take a brief look at the following example code.

CROSSWORD-SIZE= 195
Let [diagram] binary! CROSSWORD-SIZE

change/repeat diagram 0 CROSSWORD-SIZE

COLUMNS: 13
ROWS: 15

for vertikal ROWS [
    if is-odd vertikal [
       for horizontal COLUMNS [
           if is-odd horizontal [
              field: (vertikal - 1) * 13 + horizontal
              poke diagram field 1
           ]
       ]
    ]
]

; now we check if the diagram is filled with 1 where the row and column numbers are odd
for i 15 [
    for j 13 [
       write pick diagram (((i - 1) * 13) + j)
       write " "
    ]
    write new-line
]

What we see as a result is

1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 5 0
0 0 152 18 0 0 0 4 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0 0 0 0
0 0 0 0 255 255 255 255 255 255 255 255 10
0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0

What happened here? Let us check using the following code

for index 195 [
    write pick diagram index
    either 0 = modulo index 13 [ write/line ""][write " "]
]

Now this results in

1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1

Which is the result we hoped for.

So what did happen? The i and j variables were interpreted as INTEGER8! for both 15 and 13 fit in this nice small type. The calculated result for the location to look in the BINARY! ARRAY! diagram is also of this type and some of its result are thus negative and the PICK is done out of the range for the array. The second method has no problem like this.

Solving this requires to put a definition of i and j prior to their use inside the FOR loops.

i: to integer! 0
j: to integer! 0

That is it.

Errors that do not seem to make sense

What if your script is done and you just changed a few things and here comes...

** Script hint: ** Script error: cannot use pick on none! value
** Where: thing-of either reduce either log -apply- apply abort-unset if check-undefined foreach either
   unless -apply- apply if match-table if match-method any either compile-expression foreach emit-C-program
   either do either either either -apply-
** Near: thing-of first inputs-of code

In this case I messed up a JOIN/WITH of some strings that I wanted to put together. I needed 1 more JOIN/WITH.

Other times your program returns lots of info, much more than you expected. When you have a JOIN/WITH too many, the next thing will be joined as well. And if this is your resulting string again, this will be added again and it grows and grows.. too long!

Compile errors C

But what are they trying to say to you? Let us take a look at the following example

clang: warning: argument unused during compilation: '-no-pie' [-Wunused-command-line-argument]
In file included from <built-in>':1:
/var/lib/caddy/compile/APE/cosmopolitan.h:28194:9: warning: 'IFNAMSIZ' macro redefined [-Wmacro-redefined]
#define IFNAMSIZ    IF_NAMESIZE
        ^
/var/lib/caddy/compile/APE/cosmopolitan.h:28091:9: note: previous definition is here
#define IFNAMSIZ 16
        ^
out.c:554:33: error: assigning to 'none_t' (aka 'void *') from incompatible type 'floater64_t' (aka 'double')
                                        ((global_domainHxHstart = (floater64_t) atof (global_word)))
                                                                ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
out.c:558:31: error: assigning to 'none_t' (aka 'void *') from incompatible type 'floater64_t' (aka 'double')
                                        ((global_domainHxHend = (floater64_t) atof (global_word)))
                                                              ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
out.c:638:32: error: invalid operands to binary expression ('floater_t' (aka 'double') and 'none_t' (aka 'void *'))
                                        (false || (global_xHstart > global_domainHxHstart))
                                                   ~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~
out.c:645:30: error: invalid operands to binary expression ('floater_t' (aka 'double') and 'none_t' (aka 'void *'))
                                        (false || (global_xHend < global_domainHxHend))
                                                   ~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~
1 warning and 4 errors generated.

What happened here? First of all, your Meta code did all right, you have entered the phase where the CLANG compiler has tripped over.

If you take a look at this, you see that something went wrong with assigning a value. In this case I had tried to set the value of a variable back to a not used state. But this variable had already been used as a FLOATER! type value. As with integer values you can use these in IF statements:

n: 0
if n [write/line "this will not be printed"]
n: 1
if n [write/line "this will be printed"]

but this cannot be done with floating point values, so I tried to use

fp: 1.2 ; say it had some value before
fp: none ; 'reset to none'
if not is-none fp [write/line "fp has a value!"]

But that is not possible at this level. (For now, dynamic types will be added, they are on the wishlist). Now I use a separate LOGIC! value.

is-fp-set?: false ; reset fp
if is-fp-set? [write/line "Yes fp has a value we can use"]

Compile errors C another example

Now take a look at the following example, that almost looks identical to the untrained eye.

clang: warning: argument unused during compilation: '-no-pie' [-Wunused-command-line-argument]
In file included from :1:
/var/lib/caddy/compile/APE/cosmopolitan.h:28194:9: warning: 'IFNAMSIZ' macro redefined [-Wmacro-redefined]
#define IFNAMSIZ    IF_NAMESIZE
        ^
/var/lib/caddy/compile/APE/cosmopolitan.h:28091:9: note: previous definition is here
#define IFNAMSIZ 16
        ^
out.c:830:9: error: expected expression
                                                                {
                                                                ^
out.c:830:9: error: expected ':'
                                                                {
                                                                ^
                                                                :
out.c:829:67: note: to match this '?'
                                                                                                                                                        if (! ((global_lineHtype == global_typeHlabel) ?
                                                                                                                                                                                                       ^
out.c:830:9: error: expected expression
                                                                {
                                                                ^
1 warning and 3 errors generated.
./math-diagram: regel 1: clang:: opdracht niet gevonden
./math-diagram: regel 2: built-in: Bestand of map bestaat niet
./math-diagram: regel 3: /var/lib/caddy/compile/APE/cosmopolitan.h:28194:9:: Bestand of map bestaat niet
./math-diagram: regel 5: ^: opdracht niet gevonden
./math-diagram: regel 6: /var/lib/caddy/compile/APE/cosmopolitan.h:28091:9:: Bestand of map bestaat niet
./math-diagram: regel 8: ^: opdracht niet gevonden
./math-diagram: regel 9: out.c:830:9:: opdracht niet gevonden
./math-diagram: regel 17: syntaxfout nabij onverwacht symbool '?'
./math-diagram: regel 17: `                                                                                                                                                        if (! ((global_lineHtype == global_typeHlabel) ?'

But it lacks the information we had in the previous example. Not really an idea what is happening now. So this is probably caused by your code that contains a loop construct that is inside some function that expects a value.

When I had a complex stack of conditionals going on

while not is-tail file-handle [
    while all[][
        unless error? [
            any [
                if line-type = TYPE-LABEL [
                    either[][
                        while/for []; <= with this layer added: error, without: no more error
                    ]
                ]
            ]
        ]
    ]
]

First, WHILE NOT would be better as UNTIL. Because I regulargly test my programs during development and at least test if compiling is still working, facing this error and reversing the last additions seeing it solved must give the clue to the solution.

Kaj describes the situation as: "e; The C generator is hitting the nasty C limitation here that loops can't be in expressions that return a value. And the clauses in the ANY need to return values for ANY to select on "e;

To resolve this issue it is best to help the compiler a bit on this by adapting the construction used.

unless any [
    if case-1 []
    if case-2 []
][
    either case-3 [
        repeat n []
    ][
        any [
            if case-4 []
            if case-5 []
        ]
    ]
]

Or perhaps even only split out the troublemakers

unless any [
    if case-1 []
    if case-2 []
    if case-4 []
    if case-5 []
][
    if case-3 [
        repeat n []
    ]
]

UNLESS needs an additional BLOCK! here. Another solution would be keeping a LOGIC! value for the score on if one condition within the ANY has been matched. This defeats the purpose of the ANY though and should only be done as an escape if everything else fails (or better said it seems to fail!).

Runtime error: Segmentation fault

This notification is on my system in Dutch, you can figure out what it will say on your own machine.

function-data Segmentatiefout (geheugendump gemaakt)

Memory dump was made. But it is a segmentation error. This means your Meta compilation is good, the C compilation step was good. You are on the right way, but now?

Usually you get this when you use a variable (WRITE) when it has not yet been initialized.

Using inline Assembly in your programs?

The Metaproject site has a example of inline assembly used for an Atari program. Other assembly is probably not in the picture yet, unsure if it ever will. For machines like the Atari it can be useful to be able to use assembly, if only for recreating existing examples in Meta.

The example that you can check out is on the Meta site, it is in the Rainbow program there.

More tips needed?

Tip me!