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 "
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.
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.
There are basically three data types:
'a' 'b' OR ." is 'c'. Beware that the printout of these operations depends on the present character set, and in certain cases may disrupt the terminal behaviour. Characters are not compatible with any other type (BOOLEAN or NUMERIC). To get a boolean operation on characters, use "
'xxxx' = ' '", which will give a BOOLEAN vector/array (or scalar). It is normal to regard
' 'as 0. Example of using ASCII, BOOLEAN 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.
#FALSE", being scalar 0, and "
#TRUE", being scalar 1.
-1 0.5 POWER DUP MULTIPLY" [sqr(sqrt(-1))] is -1. Numeric values are internally represented by:
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. (Beware: presently it works only for INTEGER and REAL arguments, not on COMPLEX, QUATERNION, OCTONION -- to be implemented soon. The multi-dimensional numbers leave 'nan' in place.).
There are three additional stack data types:
ASSIGN" and "
SET" operators, and can be used also by the "
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 "
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. "
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: "
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!
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 "
ARGS ". 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 "
Structured vectors/arrays can not be inputed directly, but must be built up with
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 boolean 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.
Boolean FALSE is equivalent to the number 0, and is printed so. Boolean TRUE is equivalent to the number 1, and is printed so. The boolean operations operate on all numbers, and take that 0 (inside the comparison tolerance QuadCT) is FALSE (the boolean is 0), any other value is TRUE, and gives as the boolean 1. Results of boolean operations are always only the integers 0 or 1.
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.
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:
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.
Functional, variable and label data are stored under their name. To
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 "@".
_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 ".").
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.
FUNCTIONis a sentence starting with "
FUNCTION" and ending with "
ENDFUNCTION". 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
FUNCTIONword and the "
;" word there can be no "
END"). This is rather obvious, as the "
." initiates the execution of the sentence, and a half finished
FUNCTIONis nothing you can execute. A lexical analyser error will be given if this condition is not satisfied. A
FUNCTIONheader has two forms. Just "
FUNCTION" gives a niladic function (function with no arguments). "
ARGS" 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. Functions can be recursive. For examples, please see Appendix 1.
ARGS 1 FUNCTION", for the convenience in defining user functions, as well as in formating sentences with EACH. For example "
MONADIC SHAPE; EACH".
ARGS 2 FUNCTION", for convenience in defining user functions, as well as in formating sentences with short
;") marks the end of a
FUNCTIONdefinition, and "
;" also returns from that function, leaving the TOS unchanged.
;") can not be used inside a
FUNCTION, as it is a syntactial element in Virtue, and always terminates the function definition. Please see
FUNCTIONfor more. Beware: the whole
FUNCTIONdefinition ending with
;") has to be in one (1) sentence!
RETURNoperator 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_YESis an operator modifier. An operator or a function will be executed if the previous
CHECKyielded TRUE, based on its (
CHECK's) argument. From the last
CHECKoperator till the end of the sentence (or till the next
CHECKoperator) each operator modified by the
IF_YESmodifier will be executed. Contrary, if the previous
CHECKyielded FALSE, all
IF_YESmodified 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_YESoperator modifier, but
FUNCTIONs can. A
FUNCTIONwill be automatically executed (as with the
EXECUTEoperator) if the
CHECKcondition was TRUE, and skipped (and popped from the stack) if FALSE. An
IF_YESoperator at the benining of a sentence behaves as a
IF_NOwork in a non-fuzzy way, i.e. there is only true or false truth.
IF_NOis an operator modifier. An operator or a function will be executed if the previous
CHECKyielded FALSE, based on its (
CHECK's) argument. From the
CHECKoperator till the end of the sentence (or till the next
CHECKoperator) each operator modified by the
IF_NOmodifier will be executed. Contrary, if the previous
CHECKyielded TRUE, all
IF_NOmodified 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:' PRINT IF_YES DISCARD 'A vector of 6 random numbers:' PRINT IF_NO DISCARD 6 6 RESHAPE ROLL PRINT +REDUCE 'Your score is:' DISCARD IF_NO 'The sum of these numbers is:' DISCARD IF_YES SWAP CATENATE PRINT LEFT IF_NO LEFT IF_NO 'Thank you for gambling.' DISCARD IF_NO PRINT IF_YES LEFT LEFT LEFT."Naturally, this is a very silly example. Data, variables and addresses can not yet be modified by the
IF_NOoperator modifier, but
FUNCTIONs can. A
FUNCTIONwill be automatically executed (as with the
EXECUTEoperator) if the
CHECKcondition was FALSE, and skipped (and popped from the stack) if TRUE. An
IF_NOoperator at the benining of a sentence behaves as a
IF_YESwork in a non-fuzzy way, i.e. there is only true or false truth.
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) --- 6end exit into the operating system.
All operations operate on the Top Of Stack (TOS) and leave the result on TOS.
-1.2 CEILING" is -1, "
1.2 CEILING" is 2, "
3.4i2.5 CEILING" is 3i3).
1.1 CONJUGATE" is 1.1 Complex: "
1i1 CONJUGATE" is 1i-1 Quaternion: "
1i1j1k1 CONJUGATE" is 1i-1j-1k-1
-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
1 EXPONENTIAL" is e (2.71828...) with as high precission as possible on a given computer/os/compiler
5 FACTORIAL" is 120), gamma function for all other numbers ("
5i5 FACTORIAL" is -0.974395i2.00669)
-1.2 FLOOR" is -2, "
1.2 FLOOR" is 1, "
3.4i2.5 FLOOR" is 3i2.
-17 MAGNITUDE" is 17 Complex: "
-17i17 MAGNITUDE" is 24.0416 Quaternion: "
-17i-17j-17k-17 MAGNITUDE" is 34
NATURALLOG EXPONENTIAL" is x. For example "
1i1 NATURALLOG EXPONENTIAL" is 1i1. Please take into the account the possible imprecissions produced by the computer.
(1 0) NOT" is (0 1).
1 PITIMES" is 3.14159...
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
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. ...
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)).
CHECKis used in conjunction with the
IF_NOoperator modifiers (for more explanation see
BOOLEANscalar or one element vector, giving the truth value of the check. This value is saved internally, and is available for all consequent
IF_NOoperator modifiers until the end of the sentence, or until the next
CHECKdiscards the data on TOS.
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.
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.
24 INTERVAL (2 3 4) RESHAPE" gives
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.
RAVELof a scalar gives a vector of length 1.
'' 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" again gives: 1 2 3 4 and "
FIRST" again: 1 Any further application of
FIRSTwill give the scalar itself.
RESTare principally the same as CAR (
FIRST) and CDR (
REST) in Lisp.
RESTof 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 (' ').
RESTwill never change the rank (dimensionality) of the argument. For example, applied to the "
24 INTERVAL (2 3 4) RESHAPE" (see
REST" will produce:
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:
FIRST" took the first (leftmost) dimension of the 3-dimensional array resulting from the first "
RESTare principally the same as CAR (
FIRST) and CDR (
REST) in Lisp.
SHAPEof a scalar, i.e. whose shape is NIL. For example: "
(1 2 3) (2 4) RESHAPE ENCLOSE" results in
MONADIC SHAPE; EACH" will produce the SHAPE of the enclosed element (or more of them, if
CATENATEd). For the above example it will produce:
DISCLOSEis the left inverse of
DISCLOSEworks 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.
FIRST" - "
1 DEPTH" gives 0, "
(1) DEPTH" gives 1. "
1 ENCLOSE ENCLOSE ENCLOSE DEPTH" is 3.
REDUCEis 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
REDUCEhas 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).
REDUCEdoes not support COMPLEXED vectors/arrays yet. A
REDUCEoperator at the begining of a sentence behaves as a
REDUCE(see above). The only difference with
REDUCEis 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).
REDUCEFIRSTAXISdoes not support COMPLEXED vectors/arrays yet.
REDUCEFIRSTAXISpresently does not support non-commutable operators, i.e.
REDUCEFIRSTAXISoperator at the begining of a sentence behaves as a
FUNCTIONstays 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
FUNCTIONfor further description.
EACHapplies the function, which is on TOS, to each element of the space (vector/array) on TOS-1 in case of a monadic
FUNCTION, or on TOS-2 and TOS-1 in case of a dyadic
FUNCTION. A monadic
FUNCTIONis defined as "
ARGS 1 FUNCTION", a dyadic one as "
ARGS 2 FUNCTION". For any other function (i.e. niladic or multiadic)
EACHwill return an error. To get the function on TOS, just use the variable name into which the
ASSIGNed - the same way as for
FUNCTIONcan 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
FUNCTIONis 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
JUMPhas one argument, which is actually always an integer. "
0 JUMP" has the same effect as
RETURNhas, 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).
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).
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).
(1 1 0 0) (1 0 1 0) AND" is (1 0 0 0).
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).
|"||"-7"||hyperbolic arcus tangens.|
|"||"-6"||hyperbolic arcus cosinus.|
|"||"-5"||hyperbolic arcus sinus.|
0 #COS CIRCULAR" gives 1
0 (#SIN #COS #TAN) CIRCULAR" gives (0 1 0)
0 (1 2 3) CIRCULAR" gives (0 1 0)
(0 1 2) (#ACOS #ASIN #ATAN) CIRCULAR" gives (1.5708 1.5708 1.10715)
MAGNITUDE, i.e. "
3i3 3i2 GREATER" is equivalent to "
3i3 MAGNITUDE 3i2 MAGNITUDE GREATER".
2 16 LOGARITHM" is 4.
MAGNITUDE. Example: "
3i3 3i2 MAXIMUM" is 3i3.
MAGNITUDE. Example: "
3i3 3i2 MINIMUM" is 3i2.
2i2j2k2 10i9j8k7 MULTIPLY" gives -28i36j40k32, "
10i9j8k7 2i2j2k2 MULTIPLY" gives -28i40j32k36.
(1 1 0 0) (1 0 1 0) NAND" is (0 1 1 1).
(1 1 0 0) (1 0 1 0) NOR" is (0 0 0 1).
(1 1 0 0) (1 0 1 0) OR" is (1 1 1 0).
-10 0.25 POWER" is 1.25743i1.25743.
(-10 7i10 0.3) (17 5 10) RESIDUE." is (-3 -5i7 0.1).
Non-scalar dyadics work on structures, and do not produce a mathematical, but a structural result.
(3i1 4i2 5i3) 1i2j3k4 CATENATE ' ab' (1 2 3) , CATENATE" gives (3i1 4i2 5i3 1i2j3k4 ab 1 2 3).
<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.
RESHAPEstructures 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
10 INTERVAL (2 18) RESHAPE" gives
RESHAPEwill 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
10 RESHAPE" we get the original vector (the result of "
10 INTERVAL"): (1 2 3 4 5 6 7 8 9 10).
ASSIGNis 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.
ASSIGNworks 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
FUNCTIONassignment would be wiser to have been written: "
FUNCTION 3; @.a ASSIGN EXECUTE 1 ADD .a EXECUTE ADD"
SETis 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
FUNCTION 3; @.a SET .a EXECUTE 1 ADD .a EXECUTE ADD"
IFis a dyadic operator which checks the TOS-1 for a boolean value. The
IFwill produce NIL on TOS if the TOS-1 is #FALSE, otherwise leaves the TOS unchanged. It is commonly used with
JUMPwill 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
IFmay 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
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.
1 2 RIGHT" gives 2.
4 DUP MULTIPLY" gives 16.
2 4 SWAP DIVIDE" gives 2.
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" ENDFUNCTION "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)" ENDFUNCTION "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: .Magnet 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.
To use the .Magnetic
FUNCTION defined above 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 TOS print, i.e. it will print TOS after each sentence, but will not produce any other output, except error messages.
The following file shall be saved as "Magnetic.virt":
" .Magnetic " "This function calculates the magnetic environment of a conductor. It is a 5 arguments function, the usage is:
.Magnet 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:" 'The results for ''2.3 100 10i10j10 30i30j30 15i15j15 .Magnetic EXECUTE.'':'. 2.3 100 10i10j10 30i30j30 15i15j15 .Magnetic EXECUTE. "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:" 'The results for ''2.3 100 10i10j10 30i30j30 15.5i15.5j15.5 .Magnetic EXECUTE.'':'. 2.3 100 10i10j10 30i30j30 15.5i15.5j15.5 .Magnetic EXECUTE. "And do not forget the OFF, if you use this file in non-interactive mode." OFF.
Start the batch processing of the above file ('Magnetic.virt') as follows:
cat Magnetic.virt | Virtue -q > Magnetic.results
and the results of processing will be saved in the file 'Magnetic.results'.