Virtue 0.3 n0.41.8

General description

Virtue is a mathematical processor shaped after ideas from APL, mathematics and other language systems. Its final aim is to implement the "knowledge" of mathematics and other related areas in a simple and consistent framework.

It is a vector/array processor. All presently implemented operations are scalar. This means that no matrix multiplication/division or similar is implemented (the so called inner products). However, as there are complex numbers, quaternions and octonions (not implemented fully yet, so they can presently not be generated or inputed), all matrix operations are possible using those multidimensional numbers instead of vectors/arrays of singledimensional numbers. The inner product (matrix product) operations will be implemented in next development stages.

Scalar operations work on two scalars, scalar and vector/array or two vectors/arrays of the same rank.

Virtue is a Reversed Polish Notation (RPN) processor with a stack. Operations are in principle niladic (no arguments), monadic (one argument, the Top Of Stack - TOS) or dyadic (two arguments, TOS-1 op TOS), although defined functions can be n-adic (defined by the function definition). However n-adic functions can not be used as scalar operators, even with "EACH".

Workspace

The 'workspace' is the whole environment of the Virtue. It contains all the data on the stack, as well as general variables like QuadCT, QuadIO (see further in text - presently they are not changeable), the "ASSIGN"ed or "SET" variables as well as FUNCTIONs. The sentence inputed at the Virtue prompt is not regarded as part of the workspace, and is active until it's execution is finished. After that moment it is discarded.

Presently there are no 'workspace save' or 'workspace load' operators implemented, so pleas keep all your valuable texts (programmes) for Virtue in a separate windowed text file, and just copy-paste them into Virtue.

Execution interruption:

If the sentence inputed requires very long execution, and you do not like to wait until it finishes, it is always breakable by using the control-c (^c) break character, as common on UNIX. The state of the top of stack is indeterminate. It could contain, for example, an array with half finished results. The data on stack below the top of stack is in most cases preserved, but it is certain only that the data below that point on stack is unchanged. When FUNCTIONs are executed the top elements of the stack shall be examined thoroughly before assuming their correctness.

Data types

There are basically three data types:

ASCII

characters. Characters are not compatible with any other type (LOGIC or NUMERIC). However, in LOGIC operations a character will take a value of 0 if it is a ' ' (space) or a control character, and 1 if it is printable. A space (' ') is taken as a LOGIC 0, as it is actually an empty character. Example of using ASCII, LOGIC and NUMERIC: The number of words in a sentence is (usually, if no double spaces) 'one more than the number of spaces', in Virtue: "'Hello, how are you today?' ' ' EQUAL ADD REDUCE 1 ADD." giving 5.

LOGIC

LOGIC values are fuzzy logic values between FALSE and TRUE, represented as numbers in the range [0, 1]. The results of LOGIC operations are normal numbers, i.e. they can be intermixed with any NUMERIC type. The basic truth values can be input as "#FALSE", being scalar 0, and "#TRUE", being scalar 1. Logic operations (AND, OR, NOT etc.) are calculated using Łukasiewicz logic t-norm. It is important to note that to be compatible with t-norm values (i.e. inside the range [0..1]), numerical values are evaluated by assuming probabilities, i.e. the LOGIC value of a negative number is 0 (certainly not), the LOGIC value of a positive number > 1 is 1 (certainly yes), and values between [0,1] are left as is (probably). These conversion operations are done only during execution of logic operators (i.e. AND, OR, NOT etc.). Special attention is given to COMPLEX, QUATERNION and OCTONION numbers, where the 'csgn' function (see: Signum) is used to define COMPLEX, QUATERNION and OCTONION numbers < 0.

Generically 'sgn' is defined as z/|z| for any number (real, complex, quaternion, octonion etc.), 'csgn' is defined as z/sqrt(z2).

NUMERIC

numeric values come internally in several forms, but they are all compatible and follow normal mathematical rules. Therefore, i.e. "-1 0.5 POWER DUP MULTIPLY" [sqr(sqrt(-1))] is -1. Numeric values are internally represented by:

There are two special numbers which may be inputed and generated: 'inf' and 'nan'. 'inf' is infinity, and may be signed (inf, -inf). For example "1 0 DIVIDE" gives 'inf', "-1 0 DIVIDE" gives '-inf', "1 1 0 DIVIDE DIVIDE" gives 0. 'nan' is Not a Number, i.e. when there is no mathematical solution, as for example in "0 0 DIVIDE", which gives 'nan'. It is produced on all operation singularity points. It is important to note that neither '±inf' nor 'nan' will disallow further processing, i.e. there is no 'domain error' in Virtue. This is mathematically correct, as those numbers can occur only on singularities. Remark: as anything taken 0 times, i.e. multiplied by 0 is 0, be the anything a number or a thing, both '±inf' and 'nan' multiplied by 0 give the result 0. This way booleans can clean out 'nan's. "0 0 DIVIDE 0 MULTIPLY" is 0. Furthermore 'nan' and 'inf' can be normally inputed at any point as a number, generating a 'real' number, e.g: "nan" (or: "NaN"), "inf" (or: "Inf"), "-inf" (or: "-Inf"), "naninan"(or: "NaNiNaN" etc.), "infi-inf"(or: "Infi-Inf" etc.), "naninanjnanknan" or even "-infi2.5jnank2". To be able to process 'nan's and 'inf's, the comparison operations EQUAL and NOTEQUAL will compare 'nan's [i.e. "naninan naninan EQUAL" gives 1 (true)] and 'inf's, so that e.g. "1inanj-inf 1inanj-inf EQUAL" is 1 (true). Beware that, naturally, '"Inf" is not equal to "-Inf".

There are three additional stack data types:

ADDRESS

The ADDRESS is used by the "ASSIGN" and "SET" operators, and can be used also by the "SHAPE".

FUNCTION

A FUNCTION is a sentence starting with "FUNCTION" and ending with "FUNCTIONEND", shorthanded as ";". It can be "ASSIGN"ed or "SET" into a variable, and on TOS it can be "EXECUTE"d or "EACH"ed.

LABEL

A LABEL is an internal address inside one sentence, which can be used by the "JUMP" operator, and is manipulated in a similar way as a number. The label represents the Programme Counter (PC) position inside a sentence. The sentences are tokenized, and each token (number, address, operator, function etc.) takes one programme place. Therefore LABELs are actually general integers with a special tag.

Vector/array types

There are two types of vectors/arrays - simple and COMPLEXED. A simple vector/array consists of the same type of data elements, i.e. "'Hello'", or "(1 2 3)", or "(1i2 2i3 3i4)". A COMPLEXED vector/array consists of two or more data types, for example "(1 2.3 2)", or "'The value is' 2 CATENATE". Beware that "(1 2 3)" and "(1.0 2.0 3.0)" are simple vectors, the first one consisting of INTEGERs, the second of REALs. However "(1 2.0 3)" is COMPLEXED, the first and third elements are INTEGERs, the second REAL. Virtue processes COMPLEXED vectors/arrays exactly the same as simple, as long as the types are compatible for the operation [i.e. "(1 2.2 3i4j5k7.2) 1 ADD" gives (2 3.2 4i4j5k7.2), however "(1 2.2) 'a' CATENATE 1 ADD" gives the error '17 - dyadic function arguments are not conformable.']

The only actual difference with operation compatible COMPLEXED vectors/arrays, as opposed to simple ones, is that the processing of simple vectors/arrays is slightly faster than that of the COMPLEXED ones.

Vectors/arrays can also be nested, i.e. elements of a vector can contain other vectors/arrays of any shape, which can contain vectors/arrays of any shape etc. ad infinitum (actually till the memory is exhausted). There are several operators which produce nested vectors, the principal one is "ENCLOSE".

Startup

"Virtue -h" gives a short message about available options and exits. "Virtue -v" prints the version information and exits.

Virtue can be used in interactive and non-interactive mode. Interactive mode outputs several messages and a prompt after each executed sentence (actually after printing out the Top Of Stack value). Non-interactive mode is provided for using Virtue as a pipeline (see Appendix 2 for more details). The non-interactive mode is invoked by the startup flag "-q" (for quiet), i.e. "Virtue -q".

Several startup flags deal with performance measurements.

The timing startup flag is "-t" ("Virtue -t"), and will give execution times for each Virtue sentence. These execution times are real time execution times, i.e. on a busy processor they will give longer times [they are clock times, not Central Processing Unit (CPU) times].

To measure absolute and relative processor performance for each given Virtue sentence, there are the "-p" (performance) and "-pp" (processor performance) flags.

For performance measurement two different type of Virtue instructions are counted: the 'operators' and the 'operations'. 'Operators' are all Virtue words. 'Operations' are the individual operations done by the operators on individual vector/array members. For example, "1 1 ADD." executes 3 operators (the scalar 1, the scalar 1, the scalar operator ADD), and only 1 operation (the addition of 1 and 1). "(1 2 3) 1 ADD." executes also 3 operators [the vector (1 2 3), the scalar 1, the scalar operator ADD], but it executes 3 operations (1+1, 2+1, 3+1).

"Virtue -p" will print the number of operators and operations executed, which may be used for algorithm tuning, and the Virtue performance on a given computer in operators/sec and operations/sec. Beware that the same constraints as for the "-t" option are valid for all performance measurements.

The "-pp" startup flag must be followed by a space and the computer processor speed in MHz. For example, on Grgur, which is a 20MHz Sun-3, Virtue would be started with "Virtue -pp 20". On a 2.8GHz processor, you would write "Virtue -pp 2800". This flag enables the relative processor (actually computer) performance measurement, printing the amount of Virtue instructions (operators plus operations) per MHz of processor clock. When testing on several different computer architectures the same Virtue text it can be seen that certain Virtue texts are relatively faster or slower executed on different processor/hardware platforms.

When finished with the work with Virtue, the operator which stops the Virtue system is "OFF", but it has to be executed as any other operator. This means that it is also possible to put the OFF operator into a function, or in a middle of a sentence. In the moment the interpreter executes the operator OFF, it will immediately exit to the enveloping operating system. It is also possible to introduce this operator into a function, with conditional execution, for example. Do not forget to finish the sentence with the OFF operator, i.e to exit type: "OFF."

Language description

All sentences finish with "END", which can be written as ".". After receiving the end of sentence, Virtue executes the given sentence and prints out the Top Of Stack. Further sentences operate in the same way as if they were not separate, i.e. the stack is unchanged between sentences, so the following expressions are equivalent:

"1 ." !! Warning: the lexical analyser will interpret "1." as a real number 1.0, so the "." must be separated with a space after a number. However "1.." is a proper expression for a sentence containing only the real number 1.0!

"PITIMES." "@_pi." "ASSIGN." == (is equivalent to) "1 PITIMES @_pi ASSIGN."

The first row of four sentences will print out intermediate results. The second row of one sentence will print out only the final result, i.e the Top Of Stack.

Comments are written in line between the double-quotes (") and can span from empty comment ("") to any number of lines. They can be put between any words (e.g.: 1 2 "with the next operator we divide 1 by 2" DIVIDE), but, naturally, not inside a word (so, e.g.: 1 2 D"ivi"IVIDE is not recognized as DIVIDE, and the word will be ignored. It is important to note that the FUNCTION word has two forms, and no comments may be inserted between the sub-words of these forms. They are "FUNCTION", "ARGS FUNCTION". See FUNCTION operator further in the text.

The only sentences which can not be divided between lines in the abovementioned fashion are sentences which contain LABELs and "JUMP"s. The LABEL and the appropriate "JUMP"s have to be all in the same sentence. The easiest way to use "JUMP"s is using FUNCTIONs, which are a-priori one sentence texts.

The sentence results (TOS print at the end of sentence execution) are given in the smallest possible format for a number, i.e. a real number 1.0 will be printed as '1', the same as the complex number 1i0 or the quaternion 1i0j0k0. The quaternion 1i1j1k0 will be printed (and can be inputed) as 1i1j1. As all numbers are intercompatible and are automatically converted to the proper result (i.e. "10i10j10 MAGNITUDE" is a real number 17.3205, and the square root of the integer e.g. -1 will give a complex number: "-1 0.5 POWER" gives 0i1).

The input for complex, quaternion and octonion numbers is free format from left to right, i.e. "0k5" is 0i0j0k5 and "1j3" is 1i0j3, or "1j4l3o7" is the octonion 1i0j4k0l3m0n0o7. Complex numbers have the format aib for a+ib (as opposed to APL, where it is ajb), quaternions aibjckd, and octonions aibjckdlemfngoh, where a, b, c, d, e, f, g, h are numbers in any acceptable numeric format, i.e. integers, fixed point or exponential notation (xey). Therefore 0i-1.2e-24 is a properly formated complex number.

Vectors can be inputed as (a b c d ...), and strings as 'abcd...'. Multidimensional arrays can not be inputed directly, but must be reshaped (see "RESHAPE").

Structured vectors/arrays can not be inputed directly, but must be built up with ENCLOSE and CATENATE.

The vector/array index origin (QuadIO) is always 1 in the present implementation. There will be a possibility to choose the index origin as 0 or 1 in the future.

Certain operations, like LOGIC operations, comparison operations, use a comparison tolerance, i.e. they will claim that e.g. two numbers are equal if they do not differe more than QuadCT, which is presently set to 1e-13. In a later version QuadCT will be assignable by the human.

LOGIC FALSE is equivalent to the number 0, and is printed so. LOGIC TRUE is equivalent to the number 1, and is printed so. The LOGIC operations operate on all numbers, and take that 0 (inside the comparison tolerance QuadCT) is FALSE (the LOGIC is 0), any other value is TRUE, and gives as the LOGIC 1. Results of LOGIC operations are always only the integers 0 or 1.

NIL

A NIL is an empty vector. It can be produced by, for example, "0 INTERVAL", and is the shape of a scalar (e.g. "4 SHAPE" gives NIL). The NIL on the TOS is printed as a single dot ('.'). Any monadic or dyadic scalar operation operating on a NIL (any argument or both) gives NIL. The shape of NIL is a one-element vector with the only element being 0 [i.e. (0)]. So: "4 SHAPE." gives . [NIL] "SHAPE." of the above gives 0 "SHAPE." of the above gives 1.

NIL can explicitly be inputed as "#NIL", producing a vector of length 0. NIL can also be regarded as representing an empty set. It will catenate with anything, giving as the result only what was catenated with it.

NIL is used by the JUMP operator, and is also produced by the IF operator. The IF will produce NIL on TOS if the TOS-1 is #FALSE, otherwise leaves the TOS unchanged. JUMP will jump on any address, except 0 and NIL. On NIL JUMP will not not do anything, i.e. "#NIL JUMP 1" gives the result 1.

Names and Variables

Virtue has four main name spaces inbuilt, and they are distinguished by the lexical preprocessor by special beginning characters. There are no reserved words in Virtue. All Virtue operators have one or more synonymous names and a special sequence of characters from the APL character set.

All operator names are all capitals. They are fixed by the implementation.

To allow a full character name space for variables and labels, they start with the following special character:

"_abcd"

a variable name provided for data

".abcd"

a variable name provided for functions

"%abcd"

a label name

There are two name spaces for user variables, it is supposed to use _ID for variables containing data and .ID for variables containing functions, but there is no restriction imposed.

Addresses and Variables

Functional, variable and label data are stored under their name. To ASSIGN or SET the value to a name an address is necessary. The address in a sentence shows the interpreter where to save the data, i.e. which name in the symbol table will hold the value (data, function, label). The name itself in a sentence shows the interpreter where to get the data (from which name to take it), and then put on the TOS.

In the previous paragraphs we described the variable names. Putting the variable name in a sentence will in the moment of execution take the value from the named variable and put it on the stack.

To specify the internal position where a value can be assigned to, based on the name, and address must be specified, begining with the address character "@".

Example: "_a." [if _a was never before assigned in the workspace] gives 23 - value has been passed an empty variable, i.e. can not use un-initialized variable.

Now, if we want to put a value through the address into a variable we shall write: "3 @_a SET." This will leave the stack unchanged. Now the first sentence "_a." will give the number 3 as TOS (and print it out, as it is a selfstanding sentence - ends with ".").

Numbers

The number space has the normal numerical characters, and constants are inputed in a fashion commonly used in computers. So there is 1, -1, -1.0, 1.0, 1e10, 1e-10, up to the complexity of -1.234e-12.458 ...

Complex numbers are a sequence of two numbers represented above, with the imaginary part separated by the letter "i", as common in mathematics (and not the letter "j", as it is in APL!).

Quaternions are a sequence of 3 or 4 numbers separated by "i", "j" and "k". They can be inputed with in such a way that all the zeros are left out, except the first one, i.e. "0k1" (the number 0i0j0k1) or "0j1" (the number 0i0j1k0), or "0j1k1" (the number 0i0j1k1).

Octonions are a sequence of 5, 6, 7 or 8 numbers separated by "i", "j", "k", "l", "m", "n", "o", and follow the same shorthand conventions as the quaternions described above. "0o1" is the number 0i0j0k0l0m0n0o1.

As the mathematics of quaternions and octonions necessitates a lot of processing per number dimension, the Virtue interpreter processes differently dimensioned quaternions and octonions with with shortened mathematical procedures. This means that "1i2j3 2i3j4 MULTIPLY" is executed much faster than, for example, "1i2j3k1e-300 2i3j4e1-300". The interpreter will take only a pure 0.0 in the last dimension as signifying a 3-dimensional number. The same applies specially to the octonions, whose mathematics is extremely compute intensive.

Operators

Niladics

"FUNCTION"

A FUNCTION is a sentence starting with "FUNCTION" and ending with "FUNCTIONEND". It can be "ASSIGN"ed or "SET" into a variable, and on TOS it can be "EXECUTE"d, or "EACH"ed. The whole function definition has to be in one (1) sentence, i.e. between the FUNCTION word and the ";" word there can be no "." ("END"). This is rather obvious, as the "." initiates the execution of the sentence, and a half finished FUNCTION is nothing you can execute. A lexical analyser error will be given if this condition is not satisfied. A FUNCTION header has two forms. Just "FUNCTION" gives a pletoradic function (function with no defined number of elements, i.e. zero or more). A pletoradic function can be used as a pure Lambda function, it is unnamed, and has any number of arguments. "ARGS <n> FUNCTION" gives an n-adic function. The number of arguments of the function can be any, but the result generally has to be just one (on TOS), although it is allowed to leave any number of results on the stack, as the result of a FUNCTION. However, in that case, the caller has to know it and deal with it. FUNCTIONs which are "EACH"ed must be monadic ("ARGS 1 FUNCTION" ... ";") or dyadic ("ARGS 2 FUNCTION" ... ";"), and have to take scalars as input and leave only one result on stack. Although Virtue will make every effort to catch possible calling convention irregularities for the "EACH" operator, generically the behaviour of Virtue if an "EACH"ed function does not follow this scalar convention is not defined. Beware that a substructure in this sense is actually behaving as a scalar. Any non-scalar result of the "EACH"ed function will be interpreted as a substructure, i.e. the result of the "EACH" will be a (sub-)structured vector/array. Functions can be recursive. For examples, please see Appendix 1.

"NILADIC"

This is actually an alias for "ARGS 0 FUNCTION", for the convenience in defining user functions.

"MONADIC"

This is actually an alias for "ARGS 1 FUNCTION", for the convenience in defining user functions, as well as in formating sentences with EACH. For example "MONADIC SHAPE; EACH".

"DYADIC"

This is actually an alias for "ARGS 2 FUNCTION", for convenience in defining user functions, as well as in formating sentences with short DYADIC inlined functions.

"FUNCTIONEND"

The semicolon (";") marks the end of a FUNCTION definition, and ";" also returns from that function, leaving the TOS unchanged. FUNCTIONEND (";") can not be used inside a FUNCTION, as it is a syntactial element in Virtue, and always terminates the function definition. Please see FUNCTION for more. Beware: the whole FUNCTION definition ending with FUNCTIONEND (";") has to be in one (1) sentence!

"RETURN"

The RETURN operator behaves exactly the same as "0 JUMP", it actually finishes the processing of a sentence; so it is also the return from a function. Example: "1 RETURN 2 ADD" gives 1.

"IF_YES", "?Y"

The IF_YES is an operator modifier. An operator or a function will be executed if the previous CHECK yielded TRUE, based on its (CHECK's) argument. From the last CHECK operator till the end of the sentence (or till the next CHECK operator) each operator modified by the IF_YES modifier will be executed. Contrary, if the previous CHECK yielded FALSE, all IF_YES modified operators will be ignored (skipped). This is the same as the "y:" modifier in the Pilot language. Example: "1 CHECK 2 3 ADD IF_YES SUBTRACT IF_NO" gives the result 5. "0 CHECK 2 3 ADD IF_YES SUBTRACT IF_NO" gives the result -1. For a slightly more elaborate example see IF_NO. Data, variables and addresses can not yet be modified by the IF_YES operator modifier, but FUNCTIONs can. A FUNCTION will be automatically executed (as with the EXECUTE operator) if the CHECK condition was TRUE, and skipped (and popped from the stack) if FALSE. An IF_YES operator at the benining of a sentence behaves as a NOOP. Both IF_YES and IF_NO work in a non-fuzzy way, i.e. there is only true or false truth.

"IF_NO", "?N"

The IF_NO is an operator modifier. An operator or a function will be executed if the previous CHECK yielded FALSE, based on its (CHECK's) argument. From the CHECK operator till the end of the sentence (or till the next CHECK operator) each operator modified by the IF_NO modifier will be executed. Contrary, if the previous CHECK yielded TRUE, all IF_NO modified operators will be ignored (skipped). This is the same as the "n:" modifier in the Pilot language. For example, we are writing a programme which will, depending on the condition that the user is a 'gambler' or a normal user print different explanations (the value of 'gambler' shall be set, for this example, in the variable _gambler):

"_gambler CHECK
'Rolling your 6 dice I get:' WRITE IF_YES DISCARD
'A vector of 6 random numbers:' WRITE IF_NO DISCARD
6 6 RESHAPE ROLL WRITE
+REDUCE 'Your score is:' DISCARD IF_NO
'The sum of these numbers is:' DISCARD IF_YES
SWAP CATENATE WRITE LEFT IF_NO LEFT IF_NO
'Thank you for gambling.' DISCARD IF_NO WRITE IF_YES
LEFT LEFT LEFT."

Naturally, this is a very silly example. Data, variables and addresses can not yet be modified by the IF_NO operator modifier, but FUNCTIONs can. A FUNCTION will be automatically executed (as with the EXECUTE operator) if the CHECK condition was FALSE, and skipped (and popped from the stack) if TRUE. An IF_NO operator at the benining of a sentence behaves as a NOOP. Both IF_NO and IF_YES work in a non-fuzzy way, i.e. there is only true or false truth.

"NOOP"

No Operation. Nothing done, nothing changed, just a little bit of time wasted.

"OFF", "QUIT", "ENDPROCESS"

Quits the Virtue interpreter. Everything being saved in the memory and not on paper or copy-pasted to a text editor is lost once and forewer. What a pitty. But you have to have a way of stopping to work once in a while. "OFF" is a regular operator, which will work inside any function or sentence. The last TOS before the OFF operator is printed out before quitting. For example: "FUNCTION 1 2 + 3 + OFF 4 5 + ; @_a SET _a EXECUTE ." will print out:

--- Good bye. (999) ---
 
6

end exit into the operating system.

Monadics

All operations operate on the Top Of Stack (TOS) and leave the result on TOS.

"WRITE", "TOS"

Print the Top Of Stack. The stack pointer stays unchanged.

"PRINT", "QUADOUT"

Print out the Top Of Stack. This operation DISCARDS the TOS data.

Scalar monadics

"CEILING"

The result is TOS rounded upwards towards the nearest 'integer' ("-1.2 CEILING" is -1, "1.2 CEILING" is 2, "3.4i2.5 CEILING" is 3i3).

"CONJUGATE"

Mathematical conjugation. For singledimensional numbers [i.e. reals (including integers)] it is the same as the argument. "1.1 CONJUGATE" is 1.1 Complex: "1i1 CONJUGATE" is 1i-1 Quaternion: "1i1j1k1 CONJUGATE" is 1i-1j-1k-1

"DIRECTION"

Gives the unit vector of a number Real: "-7 DIRECTION" gives -1 Complex: "-7i-7 DIRECTION" is -0.707107i-0.707107 Quaternion: "-7i-7j-7k-7 DIRECTION" is -0.5i-0.5j-0.5k-0.5

"EXPONENTIAL"

Is the exponentiation with base e "1 EXPONENTIAL" is e (2.71828...) with as high precission as possible on a given computer/os/compiler

"FACTORIAL"

Pure factorial for positive integers ("5 FACTORIAL" is 120), gamma function for all other numbers ("5i5 FACTORIAL" is -0.974395i2.00669)

"FLOOR"

The result is TOS rounded downwards to the nearest 'integer' ("-1.2 FLOOR" is -2, "1.2 FLOOR" is 1, "3.4i2.5 FLOOR" is 3i2.

"MAGNITUDE"

The magnitude (hypotenuse) of the number. The result is for all numbers always a real number. Real: "-17 MAGNITUDE" is 17 Complex: "-17i17 MAGNITUDE" is 24.0416 Quaternion: "-17i-17j-17k-17 MAGNITUDE" is 34

"NATURALLOG"

Logarithm with base e. x "NATURALLOG EXPONENTIAL" is x. For example "1i1 NATURALLOG EXPONENTIAL" is 1i1. Please take into the account the possible imprecissions produced by the computer.

"NEGATIVE"

The result is 0 - the number (0-x).

"NOT"

LOGIC operation. Any non-LOGIC values will be converted to LOGIC values before performing the operation. The result of any NOT is 1 if the argument is 0 (inside the comparison tolerance QuadCT), 0 otherwise. "(1 0) NOT" is (0 1). Uses Lukasiewicz logic:  ("_x NOT" is "1 _X SUBTRACT").

"PITIMES"

pi times the argument. "1 PITIMES" is 3.14159...

"RECIPROCAL"

1 divided by the argument. "2 RECIPROCAL" is 0.5 "1i1 RECIPROCAL" is 0.5i-0.5 "0.5i0.5j0.5k0.5 RECIPROCAL" is 0.5i-0.5j-0.5k-0.5

"ROLL"

The result is a random number between the Index Origin (QuadIO), presently 1 and the number given as argument. If the given number is integer, the result will be integer, if it is a non-integer the result will be non-integer. Multidimensional numbers will "ROLL" independently in each dimension: "100i100j100k100 ROLL" gives 85.0188i40.4383j79.3099k80.844 "100i100j100k100 ROLL" gives 92.1647i20.7551j34.5223k77.823 "100i100j100k100 ROLL" gives 28.7775i56.397j48.7397k63.8871 etc. ASCII characters will roll between ' ' (space) and the given character. So for example 'z' 10 RESHAPE ROLL could give ' >elCghr1zz', or anything else.

Scalar monadics work on substructures, by applying to each element, as if there were no substructures. For example "(1 2 3) ENCLOSE (4 5 6) ENCLOSE CATENATE NEGATIVE" results in ((-1 -2 -3) (-4 -5 -6)).

Table 1: Implemented monadic operators working on data Presently implemented (Yes), not implemented (-) and not supported (X) datatypes

 

Ascii

Unicode

Integer

Real

Complex

Quaternion

Octonion

CEILING

X

X

Yes

Yes

Yes

Yes

Yes

CONJUGATE

X

X

Yes

Yes

Yes

Yes

Yes

DIRECTION

X

X

Yes

Yes

Yes

Yes

Yes

EXPONENTIAL

X

X

Yes

Yes

Yes

Yes

Yes

FACTORIAL

X

X

Yes

Yes

Yes

X

X

FLOOR

X

X

Yes

Yes

Yes

Yes

Yes

MAGNITUDE

X

X

Yes

Yes

Yes

Yes

Yes

NATURALLOG

X

X

Yes

Yes

Yes

Yes

Yes

NEGATIVE

X

X

Yes

Yes

Yes

Yes

Yes

NOT

Yes

X

Yes

Yes

Yes

-

-

PITIMES

X

X

Yes

Yes

Yes

Yes

Yes

RECIPROCAL

X

X

Yes

Yes

Yes

Yes

Yes

ROLL

Yes

-

Yes

Yes

Yes

Yes

Yes

INTERVAL

Yes

-

Yes

Yes

Yes

Yes

-

Non-scalar monadics

"CHECK"

CHECK is used in conjunction with the IF_YES and IF_NO operator modifiers (for more explanation see IF_YES and IF_NO). CHECK expects a LOGIC scalar or one element vector, giving the truth value of the check. This value is saved internally, and is available for all consequent IF_YES and IF_NO operator modifiers until the end of the sentence, or until the next CHECK operator. CHECK discards the data on TOS.

"INTERVAL"

The result of the "INTERVAL" is a vector from index origin (QuadIO - presently 1) to the integer number. The vector has 1 dimension and the number of elements is equal to the integer number. With the multidimensional numbers, "INTERVAL" gives a plane (complex numbers), a 3 or 4 dimensional 'space' (quaternions) or a 5, 6, 7 or 8 dimensional 'space' (octonions), with the multidimensional 'vector' addresses in each array location. Examples: Integer: "10 INTERVAL" is the vector (1 2 3 4 5 6 7 8 9 10) of the shape 10. Complex integer: "5i5 INTERVAL" is the 2-dimensional array 1i1 2i1 3i1 4i1 5i1 1i2 2i2 3i2 4i2 5i2 1i3 2i3 3i3 4i3 5i3 1i4 2i4 3i4 4i4 5i4 1i5 2i5 3i5 4i5 5i5 of the shape (5 5). Quaternion integer: "3i3j3 INTERVAL" is a 3-dimensional array: 1i1j1 2i1j1 3i1j1 1i2j1 2i2j1 3i2j1 1i3j1 2i3j1 3i3j1 1i1j2 2i1j2 3i1j2 1i2j2 2i2j2 3i2j2 1i3j2 2i3j2 3i3j2 1i1j3 2i1j3 3i1j3 1i2j3 2i2j3 3i2j3 1i3j3 2i3j3 3i3j3 of the shape (3 3 3) Quaternions give 3-dimensional arrays of indexes if k=0, 4-dimensional otherwise. Octonions give 5- (m=n=o=0), 6- (n=o=0), 7- (o=0), or 8-dimensional arrays. On ASCII, i.e. on characters, "INTERVAL" will make a character vector starting with ' ' and ending with the given character. For example: "'C' INTERVAL" will result in ' !"#$%&'()*+,-./0123456789:;<=>?@ABC'. "' ' INTERVAL" will give ' '.

"DECAPSULATE"

Converts a COMPLEX, QUATERNION or OCTONION scalar into a vector of the appropriate length. INTEGER and REAL numbers will make no change, COMPLEX number will make a 2 element vector, QUATERNION a 4 element, and OCTONION a 8 element vector.
Implementation restriction: For now only scalars can be
DECAPSULATEd.

"SHAPE"

Gives the shape of a vector/array. The shape is a 1-dimensional vector with as many elements as the array has dimensions, and with the particular dimensions as the vector elements. Examples: "1i2 SHAPE" is NIL [a scalar is a zerodimensional array] "7 INTERVAL SHAPE" is 7 "2i7 INTERVAL SHAPE" is (7 2) "3i4j2k7 INTERVAL SHAPE" is (7 2 4 3). This corresponds to the row major order of the arrays. If the argument to "SHAPE" is a variable address, the result is an ASCII vector of the variable name. For example: "@_distance SHAPE" is '_distance'. The variable does not have to be initialized for this operation. If the argument to "SHAPE" is a LABEL, the result is an ASCII vector of the label name.

"RAVEL"

The result is a one-dimensional vector of all the elements of an anydimensional array. Example: "24 INTERVAL (2 3 4) RESHAPE" gives

1

2

3

4

5

6

7

8

9

10

11

12

 

13

14

15

16

17

18

19

20

21

22

23

24

of the shape (2 3 4). "RAVEL" of the above array is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 of the shape 24. RAVEL of a scalar gives a vector of length 1.

"FIRST"

Selects the first item of the argument in a row major order. If the argument is empty, the result will be the 'prototype' of the argument. If the empty element is a character (type ASCII), it will result in an scalar with the character value of space (' '), and if it is a numeric empty element, the result will be 0. For example "'' FIRST" results in ' ', "0 INTERVAL FIRST" results in 0. "FIRST" takes of the first (leftmost) dimension from the argument, giving a result with rank (dimensionality) 1 less than the argument. Example: For the "24 INTERVAL (2 3 4) RESHAPE" result (see in RAVEL), the result of "FIRST" is

1

2

3

4

5

6

7

8

9

10

11

12

Applying "FIRST" again gives: 1 2 3 4 and "FIRST" again: 1 Any further application of FIRST will give the scalar itself. FIRST and REST are principally the same as CAR (FIRST) and CDR (REST) in Lisp.

"REST"

Selects all but the first item of the argument in a row major order. The REST of a scalar is #NIL (there is nothing in a scalar if you take out the first and only element - the result is an empty set). However, the type of the #NIL is ASCII if it resulted from "REST" of a scalar character. Therefore: "53 REST FIRST" is 0, "'a' REST FIRST" is a space (' '). REST will never change the rank (dimensionality) of the argument. For example, applied to the "24 INTERVAL (2 3 4) RESHAPE" (see RAVEL), "REST" will produce:

13

14

15

16

17

18

19

20

21

22

23

24

The "SHAPE" of this result is (1 3 4), i.e. it is still rank 3 (a 3-dimensional array), but the first (leftmost) dimension has only 1 element of a 2-dimensional array "SHAPE"d (3 4). Therefore applying "REST" to the result above will give #NIL [there is no non-first member of the array of shape (1 3 4)!]. To get to a lower dimension first use "FIRST" on the above [the (1 3 4) shaped array]. So: "24 INTERVAL (2 3 4) RESHAPE REST FIRST REST" produces:

17

18

19

20

21

22

23

24

[The "FIRST" took the first (leftmost) dimension of the 3-dimensional array resulting from the first "REST".] FIRST and REST are principally the same as CAR (FIRST) and CDR (REST) in Lisp.

"ENCLOSE"

Creates a scalar vector/array whose only element is the argument given. A scalar vector/array is a structure which has only one element, of the SHAPE of a scalar, i.e. whose shape is NIL. For example: "(1 2 3) (2 4) RESHAPE ENCLOSE" results in

1

2

3

1

2

3

1

2

After this scalar vector is on TOS, "SHAPE" will result in '.', i.e. #NIL. However "MONADIC SHAPE; EACH" will produce the SHAPE of the enclosed element (or more of them, if CATENATEd). For the above example it will produce:

2

4

"DISCLOSE"

DISCLOSE is the left inverse of ENCLOSE. DISCLOSE works only on scalar or one element structured vectors. The result of disclosing a vector/array which was not enclosed is the vector/array itself, i.e. no operation is done. Disclosing an enclosed structure reduces its depth by 1, and the result is a non-structured vector/array. "(1 2 3) (2 4) RESHAPE DUPLICATE ENCLOSE DISCLOSE EQUAL" is always true for any data.
NB.: For disclosing substructures of an vector/array use the "
FIRST" - "REST" combination.

"DEPTH"

The result is the depth of a structure, i.e. the number of substructure levels, the nesting levels. If there are no sub-levels, sub-structures, the depth is either 0 for scalars, or 1 for vectors/arrays: "1 DEPTH" gives 0, "(1) DEPTH" gives 1. "1 ENCLOSE ENCLOSE ENCLOSE DEPTH" is 3.

"REDUCE"

REDUCE is a special operator whose syntax includes itself and the operator word in from of it. This means that, e.g., "ADD DIVIDE" are two consecutive words, and are two consecutive dyadic operators, whereas "ADD REDUCE DIVIDE" are also TWO consecutive words ("ADD REDUCE" and "DIVIDE") and are a monadic operator ("ADD REDUCE") followed by the dyadic operator "DIVIDE". Table 2 gives the list of implemented REDUCE operations. The REDUCE has the effect of placing the operator verb (e.g. "ADD" in the previous text) between adjacent pairs of imens along the LAST axis of the TOS, and evaluating the resulting expression for each pair, from right to left throughout the vector/array. The right to left execution means that non-commutable operatons give alternating results, i.e. "(1 2 3 4) SUBTRACT REDUCE" gives -2 [(1-(2-(3-4)))]. Example: "12 INTERVAL (3 4) RESHAPE ADD REDUCE" gives (10 26 42). REDUCE does not support COMPLEXED vectors/arrays yet. A REDUCE operator at the begining of a sentence behaves as a NOOP.

"REDUCEFIRSTAXIS"

This operator has the same syntactic effect as the REDUCE (see above). The only difference with REDUCE is that it reduces the array allong the FIRST axis, as opposed to REDUCE (which reduces along the LAST axis). Example: "12 INTERVAL (3 4) RESHAPE +REDUCEFIRSTAXIS" gives (6 15 24 33). REDUCEFIRSTAXIS does not support COMPLEXED vectors/arrays yet. REDUCEFIRSTAXIS presently does not support non-commutable operators, i.e. SUBTRACT and DIVIDE. A REDUCEFIRSTAXIS operator at the begining of a sentence behaves as a NOOP.

Table 2: Implemented reduction operators (REDUCE and REDUCEFIRSTAXIS) Presently implemented (Yes), not implemented (-) and not supported (X) datatypes

 

Ascii

Unicode

Integer

Real

Complex

Quaternion

Octonion

ADD

X

X

Yes

Yes

-

-

-

AND

Binary

-

Yes boolean

Yes boolean

-

-

-

DIVIDE

X

X

Yes

Yes

-

-

-

MULTIPLY

X

X

Yes

Yes

-

-

-

OR

Binary

X

Yes boolean

Yes boolean

-

-

-

SUBTRACT

X

X

Yes

Yes

-

-

-

Control monadics

"EXECUTE"

Execute starts the execution of a function, which should be on TOS. To get the function on TOS, just use the variable name into which the FUNCTION was SET, or ASSIGN (actually after ASSIGN the FUNCTION stays on TOS and can be EXECUTEd). To allow recursivity, execute will make a new stack frame. The FUNCTIONs can have any number of arguments, but must leave only one result on the stack. See FUNCTION for further description.

"JUMP"

Jump is commonly used inside functions, although it can be used inside sentences. However, it can not jump between consecutive sentences (functions are single sentences!), as only the current sentence is kept as a programme. JUMP has one argument, which is actually always an integer. "0 JUMP" has the same effect as RETURN has, and is commonly used to stop the function execution anywhere. As seen in the example below, "0 JUMP" can also be used inside a sentence, and it will just skip the rest of it, as also in functions. "#NIL JUMP" will just continue, and forget the JUMP. Any other number on TOS will force the interpreter to continue the execution from that word in the sentence. Commonly the positions of the words in a sentence are represented by LABEL names, although the value of a LABEL is just an integer. For example: "(1 2 3) 5 JUMP (5 6 7) 1 ADD." gives (2 3 4), the (5 6 7) is skipped. From this example it is obvious that each operator or structure [e.g. (5 6 7)] is one word in a Virtue sentence. However, the above sentence would be much more readable (and allow for modification) by usage of labels. So: "(1 2 3) @%one JUMP (5 6 7) %one 1 ADD." gives (2 3 4). "(1 2 3) #NIL JUMP (5 6 7) 1 ADD ADD." gives (7 9 11). "(1 2 3) 0 JUMP (5 6 7) 1 ADD." gives (1 2 3).

Dyadics

All operations operate on the TOS-1 and TOS, discarding TOS (i.e. popping the stack) and leaving the result on the new TOS. For non-commutative operations the TOS-1 is the left argument, and the TOS the right. For example: "10 5 DIVIDE" gives 2, "5 10 DIVIDE" gives 0.5. The non-commutative operations on quaternions and octonions are also performed in traditional mathematical sense 'left op right' (e.g. "2i2j2k2 10i9j8k7 MULTIPLY" gives -28i36j40k32, "10i9j8k7 2i2j2k2 MULTIPLY" gives -28i40j32k36).

Scalar dyadics

The scalar dyadics work on scalars and scalars, scalars and vectors/arrays, and vectors/arrays and vectors/arrays of the same shape and rank (dimensionality).

"ADD", "+"

Addition. Always commutative.

"AND", "^"

LOGIC and. Any non-LOGIC values will be converted to LOGIC values before performing the operation. "(1 1 0 0) (1 0 1 0) AND" is (1 0 0 0). Uses Lukasiewicz logic: ("_x _y AND" is "_x _y MINIMUM").

"BINOMIAL", "!"

Binomial function. For nonnegative integer arguments yields the number of distinct combinations of TOS things taken TOS-1 at a time. Example: "3 10 BINOMIAL" is 45. The binomial function extends to all numbers. The coefficients of the binomial expansion (x+1)^_r can be determined by BINOMIAL using the expression "0 _r INTERVAL CATENATE _r BINOMIAL", e.g if _r is 3 then "(0 1 2 3) 3 BINOMIAL" is (1 3 3 1).

"CIRCULAR"

This is a special operator which is dyadic, although it actually performs monadic work. The left argument (TOS-1) to it is the number (or vector/array) on which the operation(s) specified in the right argument (TOS) scalar/vector/array work. The operations can be specified symbolically, or by an integer in the range of -7 to +7. (This range will be later expanded to -12 to +12 according to the APL specification of those operations.) The following operations are specified:

"#ATANH"

"-7"

hyperbolic arcus tangens.

"#ACOSH"

"-6"

hyperbolic arcus cosinus.

"#ASINH"

"-5"

hyperbolic arcus sinus.

"#SQRTX2m1"

"-4"

sqrt(-1+sqr(TOS-1)).

"#ATAN"

"-3"

arcus tangens.

"#ACOS"

"-2"

arcus cosinus.

"#ASIN"

"-1"

arcus sinus.

"#SQRT1mX2"

"0"

sqrt(1-sqr(TOS-1)).

"#SIN"

"1"

sinus.

"#COS"

"2"

cosinus.

"#TAN"

"3"

tangens.

"#SQRTX2p1"

"4"

sqrt(1+sqr(TOS-1)).

"#SINH"

"5"

hyperbolic sinus.

"#COSH"

"6"

hyperbolic cosinus.

"#TANH"

"7"

hyperbolic tangens.

Examples:

"DIVIDE"

Division.

"EQUAL", "="

Compares for equality of two ASCIIs or NUMBERs. Commutative. The result is LOGIC TRUE (1) if equal else FALSE (0). The NUMBERs are compared based on comparison tolerance (QuadCT). EQUAL compares 'nan's, i.e. two numbers are EQUAL if both of them are nan (in all number dimensions, i.e. nan = nan, naninan = naninan, naninanjnanknan = naninanjnanknan etc.)

"GREATER", ">"

The result is LOGIC TRUE if TOS-1 > TOS. Multidimensional numbers are compared by their MAGNITUDE, i.e. "3i3 3i2 GREATER" is equivalent to "3i3 MAGNITUDE 3i2 MAGNITUDE GREATER".

"LESS", "<"

The result is LOGIC TRUE if TOS-1 < TOS. Multidimensional numbers are compared by their MAGNITUDE.

"LOGARITHM"

Logarithm base TOS-1 of TOS. Example: "2 16 LOGARITHM" is 4.

"MAXIMUM"

Bigger of the two numbers. Multidimensional numbers are compared by their MAGNITUDE. Example: "3i3 3i2 MAXIMUM" is 3i3.

"MINIMUM"

Smaller of the two numbers. Multidimensional numbers are compared by their MAGNITUDE. Example: "3i3 3i2 MINIMUM" is 3i2.

"MULTIPLY"

Multiplication. Commutative for INTEGER, REAL and COMPLEX. Non-commutative for QUATERNION and OCTONION. As non-commutative TOS-1 is multiplied by TOS, e.g.: "2i2j2k2 10i9j8k7 MULTIPLY" gives -28i36j40k32, "10i9j8k7 2i2j2k2 MULTIPLY" gives -28i40j32k36.

"NAND"

LOGIC not and. Any non-LOGIC values will be converted to LOGIC values before performing the operation. "(1 1 0 0) (1 0 1 0) NAND" is (0 1 1 1). Uses Lukasiewicz logic: ("_x _y NAND" is "1 _x _y MINIMUM SUBTRACT").

"NOR"

LOGIC not or. Any non-LOGIC values will be converted to LOGIC values before performing the operation. "(1 1 0 0) (1 0 1 0) NOR" is (0 0 0 1). Uses Lukasiewicz logic: ("_x _y OR" is "1 _x _y MAXIMUM SUBTRACT").

"NOTEQUAL"

Not equal. LOGIC result is TRUE (1) if TOS-1 ≠ TOS. Commutative. The NUMBERs are compared based on comparison tolerance (QuadCT). NOTEQUAL compares 'nan's, i.e. two numbers are NOT EQUAL if any of them is nan (in any number dimension)

"NOTGREATER"

The result is LOGIC TRUE if TOS-1 ≤ TOS. Multidimensional numbers are compared by their MAGNITUDE.

"NOTLESS"

The result is LOGIC TRUE if TOS-1 ≥ TOS. Multidimensional numbers are compared by their MAGNITUDE.

"OR"

LOGIC or. Any non-LOGIC values will be converted to LOGIC values before performing the operation. "(1 1 0 0) (1 0 1 0) OR" is (1 1 1 0). Uses Lukasiewicz logic: ("_x _y OR" is "_x _y MAXIMUM").

"POWER", "*"

TOS-1 raised on the power of TOS. Even roots of negative numbers produce complex numbers, i.e. "-10 0.25 POWER" is 1.25743i1.25743.

"RESIDUE", "|"

For real positive numbers, the remainder of division of TOS-1 by TOS. Generally residue is TOS-(TOS-1)*floor(TOS / (TOS-1)). Example: "(-10 7i10 0.3) (17 5 10) RESIDUE." is (-3 -5i7 0.1).

"STRONGAND"

Lukasiewicz LOGIC strong and. Any non-LOGIC values will be converted to LOGIC values before performing the operation. For BOOLEAN LOGIC values behaves the same as AND. Lukasiewicz logic operation: ("_x _y STRONGAND" is "0 _x _y ADD 1 SUBTRACT MAXIMUM").

"STRONGNAND"

Lukasiewicz LOGIC strong not and. Any non-LOGIC values will be converted to LOGIC values before performing the operation For BOOLEAN LOGIC values behaves the same as NAND. Lukasiewicz logic operation: ("_x _y STRONGNAND" is "1 0 _x _y ADD 1 SUBTRACT MAXIMUM SUBTRACT").

"STRONGNOR"

Lukasiewicz LOGIC strong not or. Any non-LOGIC values will be converted to LOGIC values before performing the operation. For BOOLEAN LOGIC values behaves the same as NOR. Lukasiewicz logic operation: ("_x _y STRONGNOR" is "1 1 _x _y ADD MINIMUM SUBTRACT").

"STRONGOR"

Lukasiewicz LOGIC strong or. Any non-LOGIC values will be converted to LOGIC values before performing the operation. For BOOLEAN LOGIC values behaves the same as OR. Lukasiewicz logic operation: ("_x _y STRONGOR" is "1 _x _y ADD MINIMUM").

"SUBTRACT", "-"

TOS subtracted from TOS-1. Non-commutative.

Table 3: Implemented scalar dyadic operators working on data Presently implemented (Yes), not implemented (-) and not supported (X) datatypes

 

Ascii

Unicode

Integer

Real

Complex

Quaternion

Octonion

ADD

X

X

Yes

Yes

Yes

Yes

-

AND

Yes

-

Yes

Yes

Yes

Yes

-

BINOMIAL

X

X

Yes

Yes

Yes

-

-

CIRCULAR

X

X

Yes

Yes

-

-

-

DIVIDE

X

X

Yes

Yes

Yes

Yes

-

EQUAL

Yes

-

Yes

Yes

Yes

Yes

-

GREATER

Yes

-

Yes

Yes

Yes

Yes

-

LESS

Yes

-

Yes

Yes

Yes

Yes

-

LOGARITHM

X

X

Yes

Yes

Yes

-

-

MAXIMUM

Yes

-

Yes

Yes

Yes

-

-

MINIMUM

Yes

-

Yes

Yes

Yes

-

-

MULTIPLY

X

X

Yes

Yes

Yes

Yes

-

NAND

Yes

X

Yes

Yes

Yes

Yes

-

NOR

Yes

X

Yes

Yes

Yes

Yes

-

NOTEQUAL

Yes

-

Yes

Yes

Yes

Yes

-

NOTGREATER

Yes

-

Yes

Yes

Yes

Yes

-

NOTLESS

Yes

-

Yes

Yes

Yes

Yes

-

OR

Yes

X

Yes

Yes

Yes

Yes

-

POWER

X

X

Yes

Yes

Yes

-

-

RESIDUE

X

X

Yes

Yes

Yes

-

-

STRONGAND

X

X

Yes

Yes

Yes

-

-

STRONGNAND

X

X

Yes

Yes

Yes

-

-

STRONGNOR

X

X

Yes

Yes

Yes

-

-

STRONGOR

X

X

Yes

Yes

Yes

-

-

SUBTRACT

X

X

Yes

Yes

Yes

Yes

-

Non-scalar dyadics

Non-scalar dyadics work on structures, and do not produce a mathematical, but a structural result.

"CATENATE", ","

Joins the TOS-1 scalar or vector with the TOS scalar or vector. Note that only scalars and vectors may be joined, not arrays. Example: "(3i1 4i2 5i3) 1i2j3k4 CATENATE ' ab' (1 2 3) , CATENATE" gives (3i1 4i2 5i3 1i2j3k4 ab 1 2 3).

"DEAL", "?"

"<a> <b> DEAL" selects unique integers randomly from the population of "<a> INTERVAL", without repetition. The TOS argument must, naturally, be less then or equal to the TOS-1 (cannot deal 12 out of 10 cards!). If TOS and TOS-1 are equal DEAL gives a random permutation of the set of integers 1..TOS. Example: "10 10 DEAL" gives (9 4 8 10 2 3 6 5 7 1) "10 10 DEAL" gives (3 2 9 5 10 6 7 8 4 1) ... "100 0 DEAL" gives NIL.

"EACH"

EACH applies the function, which is on TOS, to each element of the space (vector/array) on TOS-1 in case of a monadic FUNCTION (i.e. a dyadic EACH), or on TOS-2 and TOS-1 in case of a dyadic FUNCTION (i.e. a triadic EACH). A monadic FUNCTION is defined as "ARGS 1 FUNCTION", a dyadic one as "ARGS 2 FUNCTION". For any other function (i.e. niladic or pletoradic) EACH will return an error. To get the function on TOS, just use the variable name into which the FUNCTION was SET, or ASSIGNed - the same way as for EXECUTE. EACHed FUNCTION can have any specification necessary (i.e. can be recursive, call other functions etc., but shall work on scalars! Working on scalars means that the function has to receive its input as a scalar (although the scalar may be a scalar array, but as a substructure), and shall leave its only result on TOS. Functions which return more than one result on stack will have unpredictable behaviour with "EACH". If the result of the FUNCTION is not a scalar but a vector/array, it will be included in the result of "EACH" as a sub-structure (the same as if normally "ENCLOSE" would have been called on this vector/array). Therefore "(1 2 3) (10 20 30) ADD" [giving (11 22 33)] and "(1 2 3) ARGS 1 FUNCTION (10 20 30) ADD; EACH." are not the same, as the second one gives as a result ((11 21 31) (12 22 32) (13 23 33))! An example for simple "EACH": To calculate 26 first Fibonnaci numbers using a recursive algorithm (the FUNCTION '.fib' can be found in the Appendix 1, and shall be defined before the example works) write: "26 INTERVAL .fib EACH" giving the result: (1 4 6 10 16 26 42 68 110 178 288 466 754 1220 1974 3194 5168 8362 13530 21892 35422 57314 92736 150050 242786 392836) Restriction: Presently only monadic EACH is implemented.

"RESHAPE"

RESHAPE structures the items of TOS-1 into an array of the shape TOS. This means that the scalar, vector or n-dimensional space on TOS-1 will become of the shape specified in a scalar or a (one-dimensional) vector. The number of dimensions of the resulting space (array) is the number of elements of the vector on TOS, and the sizes of each of the dimensions are given by the (integer) numbers in the TOS vector. RESHAPE and SHAPE are related. Examples: "1 (2 3 4) RESHAPE" gives

1

1

1

1

1

1

1

1

1

1

1

1

 

1

1

1

1

1

1

1

1

1

1

1

1

that is, a 2 rows by 3 rows by 4 columns array of 1's. "10 INTERVAL (2 18) RESHAPE" gives

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

that is 2 rows of 18 columns. Note that RESHAPE will either expand the vector to the array by repeating it from the begin as many times as necessary, or will compress it by deleting the superflous elements. If we take the result of the previous operation and apply e.g. "(2 4 13) RESHAPE" we will get

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

 

7

8

9

10

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

7

8

9

10

1

2

and finally, by applying "10 RESHAPE" we get the original vector (the result of "10 INTERVAL"): (1 2 3 4 5 6 7 8 9 10).
Restriction: Presently structured data can not be reshaped.

"ASSIGN"

ASSIGN is a dyadic operator which puts the value on TOS-1 into the address at TOS, and leaves the stack with the assigned (saved) value on the TOS for further processing. Example: "3 @_a ASSIGN" 1 ADD" gives 4, and after that "_a" puts 3 on TOS. So if we continue, "ADD" would result in 7. ASSIGN works on data and functions so, e.g. the following would give the same result as above: "FUNCTION 3; @_a ASSIGN EXECUTE 1 ADD _a EXECUTE ADD" It is though wise to distinguish functions from variables by using "_" for variables and "." for functions, although there is no restriction imposed. So, actually the above FUNCTION assignment would be wiser to have been written: "FUNCTION 3; @.a ASSIGN EXECUTE 1 ADD .a EXECUTE ADD"

"SET"

SET is also a dyadic operator which puts the value on TOS-1 into the address at TOS, but opposed to "ASSIGN" it pops both the address and the value from the stack. So, e.g. the previous example would be written with SET as: "FUNCTION 3; @.a SET .a EXECUTE 1 ADD .a EXECUTE ADD".

"IF"

IF is a dyadic operator which checks the TOS-1 for a LOGIC value. The IF will produce NIL on TOS if the TOS-1 is #FALSE, otherwise leaves the TOS unchanged. It is commonly used with JUMP, as JUMP will jump on an address, but just continue through a programme if it finds NIL on TOS. IF does not change the size of the stack, so it behaves rather like a monadic. Example: "'abc' 'abc' = ^REDUCE @%equal IF JUMP 'They difere' 0 JUMP %equal 'They are same'." gives the result 'They are same'. Although IF may be used, together with JUMP, even in directly inputed sentences, actually it is provided to be part of FUNCTIONs, not direct execution. (It is, quite obviously, easier to see, in the above example, that 'abc' is equal to 'abc', than think about the complicated IF construction).

Triadics

"MASK"

MASK is a triadic operator executing the MONADIC function on TOS for each array which is the result of applying the TOS-1 mask to the TOS-2 data. The mask is applied by multiplication, and has to have the same rank as the TOS-2 data, and the length of each of the mask's dimensions has to be odd. For an unmodified MASK it is presupposed that the data outside the TOS-2 indices is empty, i.e. 0 for numerical data or ' ' for character data. The following example shows the input to the function on TOS:

9 INTERVAL (3 3) RESHAPE 0.5 (3 3) RESHAPE MONADIC PRINT ' ' PRINT; MASK.

0 0 0
0 0.5 1
0 2 2.5

0 0 0
0.5 1 1.5
2 2.5 3

0 0 0
1 1.5 0
2.5 3 0

0 0.5 1
0 2 2.5
0 3.5 4

0.5 1 1.5
2 2.5 3
3.5 4 4.5

1 1.5 0
2.5 3 0
4 4.5 0

0 2 2.5
0 3.5 4
0 0 0

2 2.5 3
3.5 4 4.5
0 0 0

2.5 3 0
4 4.5 0
0 0 0

.
--> A #NIL is left on stack, as the exit from the FUNCTION was empty stack.

The following algorithm calculates the 'next generation' for Conway's Game of Life (let's suppose that the current generation is saved as a rank 2 array (two-dimensional matrix) in the variable _board:

_board 1 (3 3) RESHAPE MONADIC RAVEL SUM 3 EQUAL; MASK.

"EACH"

EACH applies the function, which is on TOS, to each element of the space (vector/array) on TOS-1 in case of a monadic FUNCTION (i.e. a dyadic EACH – see under Dyadics), or on TOS-2 and TOS-1 in case of a dyadic FUNCTION (i.e. a triadic EACH). Dyadic FUNCTION EACH, i.e. the triadic EACH is not implemented yet!

Stack manipulation operators

"LEFT", "DISCARD"

Take the left argument, discard the right one. This operator frees the TOS and leaves the former TOS-1 on TOS. As a dyadic operator it is called LEFT, as a monadic DISCARD. Their function is the same. If there is only one value on the stack, the result is empty stack. Example: "1 2 LEFT" gives 1.

"RIGHT"

Take the right argument, discard the left one. This operator frees the TOS-1 and leaves TOS on TOS. Example: "1 2 RIGHT" gives 2.

"DUP", "DUPLICATE"

Duplicate the value on TOS. It gives two same values on TOS. This can be used whenever a value has to be temporary preserved, and in the same time used for operations giving a second value. For example, to square a number, do: "4 DUP MULTIPLY" gives 16.

"SWAP"

Swap the TOS and TOS-1. The former TOS-1 is now TOS, the former TOS is TOS-1. Example: "2 4 SWAP DIVIDE" gives 2.

Some examples of FUNCTIONs. They can be directly copy-pasted into Virtue

"BEWARE: the functions in Virtue presently do not have local variables, so
 all variables used inside a function definition are actually side effects,
 and their changes are global. So be careful not to use the same names for
 function variables and global variables. In the future there will be
 local variables for functions in Virtue. Be patient!"
 
"The Hello world programme:"
 
'Hello world'.
 
"or, as a function:"
 
FUNCTION 'Hello world'; @.Hello SET.
 
"Execute it with:"
.Hello EXECUTE.
 
 
" This is the function which calculates the n'th Fibonacci number"
" Usage:  .fib EXECUTE ."
 
"We define a" ARGS 1 FUNCTION "as follows:"
"First" DUP"licate the Top Of Stack (TOS)."
"Now, if the Fibonacci number requested is less then 2:"
2 < ", to the label" @%a IF "it is so" JUMP
"else execute fib(TOS - one) + fib(TOS - 2). Not to have temporary variables,
the DUP operator is used:"
DUP 1 - .fib EXECUTE SWAP 2 - .fib EXECUTE ADD RETURN
"0 JUMP is exit from the function"
"now we are at the label" %a", so we" DISCARD "the user input, put" 1 "on the
top of the stack and" FUNCTIONEND "its definition."
"The variable address into which we will put the function is"
@.fib", and we just have to" SET "it."
"That's all folks".
 
"This is the same function without the superflous comments:"
 
MONADIC                                                  "i.e. ARGS 1 FUNCTION"
DUP 2 < @%a IF JUMP
DUP 1 - .fib EXECUTE SWAP 2 - .fib EXECUTE + RETURN
%a DISCARD 1;
 
@.fib SET.
 
"To get the 26'st Fibonacci number type in:"
 
26 .fib EXECUTE.
 
 
" This is the function which calculates n!:
  Usage:  .fact EXECUTE ."
" This is a recursive function, it is much faster to calculate the n!
  using  INTERVAL MULTIPLY REDUCE, but even faster (very fast) is
  to do it using  FACTORIAL. So effectively the following function is
  just a simple example of recursive functions, with no other merit than
  the measurement of computer speed. Furthermore, the FACTORIAL operator
  is defined for all numeric data and data types!"
MONADIC DUP 2 < 0 IF JUMP               "i.e. return from function if TOS < 2"
DUP 1 - .fact EXECUTE MULTIPLY                  "n*fact(n-1)"
FUNCTIONEND     "or ';'!"
@.fact SET.                                                                             "set the function name, i.e. put the
                                                                                                         FUNCTION into the variable .fact"
 
"To get 12! type in:"
 
12 .fact EXECUTE.
 
 
" This is the function which calculates n prime numbers:
  Usage:  .prime EXECUTE ."
" It is not recursive, but uses looping."
 
ARGS 1 FUNCTION
@_end SET                                                                               "put the TOS into _end"
2 1 @_t ASSIGN RESHAPE @_p SET                  "_t is 1, _p is (1)"
%Test _p _end _p SHAPE NOTGREATER 0 IF JUMP DISCARD  "_p stays on TOS if 0 JUMP"
%Add _p _t 2 + @_t ASSIGN | 0 = +REDUCE @%Add IF JUMP
_p _t CATENATE @_p SET
@%Test JUMP;    "This FUNCTIONEND (';') is never reached, as the function exits
                                         above on 0 JUMP. It must be here to end the FUNCTION."
 
@.prime SET.
 
"To get the first 100 primes do:"
 
100 .prime EXECUTE.
 
"And finally, an example of fully non-looping processing of multidimensional
 arrays using complex numbers and quaternions."
 
"This function calculates the magnetic environment of a conductor.
 It is a 5 arguments function, the usage is:
   
   .Magnetic EXECUTE.
 The current is in Amperes, the current vector, render space and position
 have to be specified in complex numbers or quaternions for the function
 to have sense."
 
ARGS 5 FUNCTION
"position in space" @_pos SET                   "the arguments are on stack from the"
"render space" @_size SET                               "last to the first imputed"
"current vector" @_dl SET
"current" @_I SET
"wire radius" @_d SET
 
1e-7 4 PITIMES MULTIPLY @_m0 SET                "constant m0"
 
_size INTERVAL _pos SUBTRACT @_rv ASSIGN
MAGNITUDE @_dist ASSIGN
_d NOTGREATER @_B ASSIGN
NOT @_A SET
 
_m0 _I MULTIPLY 4 PITIMES DIVIDE
 
"_dl _rv DIRECTION MULTIPLY" "direction does not yet work on quaternions,
                                                                                therefore it is better to use the following:"
_dl  _rv _dist DIVIDE  MULTIPLY
 
_A _dist _dist MULTIPLY MULTIPLY
_B _d _d MULTIPLY MULTIPLY
ADD
 
DIVIDE
MULTIPLY;
 
 
@.Magnetic SET.
 
"For example:"
2.3 100 10i10j10 30i30j30 15i15j15 .Magnetic EXECUTE @_mag SET.
 
"On a Linux mips (e.g. Cobalt Qube) the above expression gives a SIGFPE signal
(i.e. a Floating point exception) due to the calculation with NaN. If we move
the centre point a little bit, it will perform OK:"
2.3 100  10i10j10 30i30j30 15.5i15.5j15.5  .Magnetic EXECUTE @_mag SET.

The function .Magfield defined and used in batch processing

To use the .Magfield FUNCTION in a batch processing environment, the Virtue processor shall be used as a pipe, and it's output saved in a file. To allow batch processing Virtue accepts the '-q' flag, and it will produce only explicit Top Of Stack PRINT or WRITE, and it will print TOS after each sentence, but will not produce any other output, except error messages.

The .Magfield FUNCTION used as shown below, will produce a 4-dimensional printout of 3-dimensional spaces with a wire crossing diagonally through the given space, and the current behaving through time (the 4th dimension) as a sine wave.

The following file shall be saved as "Magfield.virt":

" .Magfield "
 
ARGS 3 FUNCTION
"current" 1e-7 "4 PITIMES MULTIPLY @_m0 ASSIGN"
                                 "constant m0 - magnetic permeability of vacuum"
          MULTIPLY "4 PITIMES DIVIDE" @_I SET         "_I is m0/4pi*I"
"conductor vectors vector" SWAP
'render space' PRINT WRITE INTERVAL @_space ASSIGN
SHAPE 0 SWAP RESHAPE @_frame SET        "make room for the final frame"
 
MONADIC
        "current vectors" DUP
        FIRST @_dl SET
        REST "'Position: ' PRINT WRITE"
        _space SWAP SUBTRACT @_rv ASSIGN
        MAGNITUDE @_dist SET
                _dl
                        _rv DIRECTION
                MULTIPLY
                _dist _dist MULTIPLY
        DIVIDE
        _I MULTIPLY
        _frame ADD @_frame SET;
EACH
 
DISCARD
_frame;
@.Magfield SET.
 
MONADIC INTERVAL MONADIC 1i1 MULTIPLY 1i1 SWAP CATENATE; EACH; "A 2-dimesional diagonal line"
        @.line2 SET.
MONADIC INTERVAL MONADIC 1i1j1 MULTIPLY 1i1j1 SWAP CATENATE; EACH; "A 3-dimensional diagonal line"
        @.line3 SET.
MONADIC INTERVAL MONADIC 1i1j1k1 MULTIPLY 1i1j1k1 SWAP CATENATE; EACH; "A 4-dimensional diagonal line"
        @.line4 SET.
 
60i60j60 @_size SET
40 INTERVAL 10 DIVIDE PITIMES #SIN CIRCULAR 1e8 MULTIPLY
        MONADIC
                _size SWAP 60 .line3 EXECUTE SWAP
                        .Magfield EXECUTE MAGNITUDE FLOOR "PRINT" DISCARD;
        EACH
DISCARD.
 
 
"And do not forget the OFF, if you use this file in non-interactive mode." OFF.

Start the batch processing of the above file ('Magfield.virt') as follows:

cat Magfield.virt | Virtue -q > Magfield.res

and the results of processing will be saved in the file 'Magfield.res'.