the Throff programming language
go get -v github.com/donomii/throff
go build github.com/donomii/throff
Or download a precompiled binary
> throff ADD 1 1
2
> throff
Welcome to the THROFF command shell v0.1. Type HELP for help.
Throff »ADD 1 1
2
throff -f properdiv.thr
result, _ := t.CallArgs1("ADD", 4, "5")
fmt.Println("Calculation result: ", result)
Throff is a dynamically typed, late binding, homoiconic, concatenative programming language, taking inspiration from Forth, Joy and Scheme. It has all the features of a modern language - closures, lexical scopes, tail call optimisations, currying, and continuations.
Everything is a function, even language constructs like IF and FOR, which can be replaced and extended with your own versions. It uses immutable semantics wherever possible to provide safe threading and continuations. There is almost no lexer/tokeniser, and no parser in the traditional sense. Commands are fed directly into the engine to be executed. The programs are written backwards.
Throff is still in development. The basic language is complete and can be used for minor tasks e.g. text processing. However some more programmer friendy features are still in development.
There are many examples in the examples and rosetta directories.
Throff programs start at the bottom and are evaluated backwards until they reach the top, where they finish. Actually, the line breaks are removed and the program becomes one long expression on a line, which is evaluated from right-to-left.
All Throff functions (bar one) operate on the result of code to the right of the function. A program is one long chain of function calls, each one transforming the output of the function to the right.
PRINTLN Hello
evaluates Hello (a string), then PRINTLN (a function). PRINTLN prints the result of the function to its right.
PRINTLN ADD 1 2
Throff processes 2, then 1, then ADDs them together, then PRINTLNs the result. If a function doesn't consume any arguments or return any values, it effectively is invisible, and so you can place it anywhere.
PRINTLN ADD .S 1 2
will call .S before calling ADD. .S prints the arguments to its right (i.e. the datastack). Very handy for debugging!
- To create a small, simple and portable interpreter (mostly complete)
- To design a language that builds itself from very simple primitives into advanced language constructs (going nicely)
- quick and effective access to platform libraries like graphics, databases, etc (not so good)
- a simple and highly configurable language (good)
- the best interactive debugger, with rewind and undo functionality (bad)
- Support advanced language features like first-class continuations (good)
- needs namespaces, monads?
- available everywhere(good)
- to minimise the use of explicit typing where possible, while still providing useful typing (nope)
Throff datatypes are still a work in progress, as I come to understand the most effective ways to structure them. At the moment, these are the datatypes, with their literal syntax:
- Boolean - TRUE, FALSE
- String - ->STRING [ This is a string ]
- Token - ANY SINGLE WORD LIKE THIS, INCLUDING PUNCTUATION [ ] , .
- Array - ->ARRAY [ one two three four ] or A[ one two three four ]A
- Code - ->FUNC [ PRINTLN Hello ]
- Lambda - [ PRINTLN Hello ]
- Hash - H[ key => value key => value ]H
- Wrapper - No literal syntax
- Bytes - No literal syntax
Note that under the hood, strings, arrays, lambdas and codes are almost the same thing, just with different flags to tell the interpreter what to do when it encounters them.
Throff is homoiconic, which in this case also means that all its data structures have explicit string representations. Every Throff data structure can be used as a string. So a simple, but slow, way to compare nested arrays is to compare their string representations:
EQUAL ->STRING ARRAY1 ->STRING ARRAY2
hashes work in a similar manner.
Native wrapper types usually will not work this way, since it is not possible to make a string representation for something like a database handle. They have a descriptive string that might be meaningful sometimes (e.g. for filehandles), but usually not.
Tokens used for manipulating source code. Printing a token returns the commands needed to recreate the token, rather than the string representation. Tokens are most useful for EVAL, and can be used to send program code over network sockets.
Internally, tokens are just strings, but they are printed differently.
Booleans are created with TRUE, FALSE and EQUAL. They are only used by the IF function, and the usual logical functions like AND and OR.
Strings and tokens are treated exactly the same, except when they are being printed out. This matters when trying to print out a data structure (or code) to be evaluated later. Throff will print strings exactly as they are. For everything else, Throff will try to print some code that will re-create the data structure, including surrounding quotes if needed. The output code might not look like the original code! Tokens are usually created by the parser, and are used for function names and variable names, while strings are created from TOKENs or directly by reading from a socket or file.
Almost everything in throff has a string representation, and wherever possible, throff acts on strings and strings alone. Every datatype except WRAPPER may be coerced into a STRING with ->STRING, or by using a function that expects a string, like PRINT or STRING-JOIN. Numbers are kept as strings, and are converted to numbers at the last moment before use.
WRAPPER types will convert into a string, but the contents might be empty or gibberish.
Variable names in throff are tokens. When throff finds a token, it tries to look it up in the local symbol table to find the value. If it has a value (if it is a variable), throff replaces the variable name with the value, then continues processing.
Because a variable name is automatically replaced with its value, it is impossible to grab the variable name (the token) itself, because it is immediately replaced with its value. However, we need access to the original tokens, because this is necessary for writing macros. Using the TOK function prevents variable lookup and gives you the token to its left.
The TOK command is the only command in Throff that acts on arguments to its left.
Hello! TOK
will force a token containing Hello! onto the datastack.
All numbers are kept as strings right up to the moment they are used in a numeric operation, then the native string to number converter is called. As a result, arithmatic is very slow.
If at any point you find yourself wondering "Is this variable a number or a string?", the answer is: it's a string.
Arrays are kept as native arrays, and are created with NEWARRAY. Under the hood, they are the same native structure as CODE and LAMBDA. All arrays that don't contain a WRAPPER have a string representation. This string is usually calculated on-the-fly, so you don't have to worry wasting memory on the string component.
Arrays may be converted to LAMBDAs with ->LAMBDA, and to CODEs with ->CODE. If you do this, the newly formed function will run in the same namespace that it was created in. You can change the environment it runs in with SETENVIRONMENT.
A lambda is a function that can be passed around like data. Lambdas are created with [ ]. Lambdas are activated by CALL.
CALL [ PRINTLN Hello ]
will result in
Hello
LAMBDAs are the fundamental component of Throff. Using the [ ] brackets always creates a LAMBDA, no matter the context. For instance, there is nothing special about the IF function - it is just a function that takes 3 LAMBDA arguments.
IF [ true ] THEN [ PRINTLN yay ] ELSE [ PRINTLN boo ]
LAMBDAs can be converted to arrays
BIND my_array => ->ARRAY some_lambda
LAMBDAs can be converted to strings
BIND my_string => ->STRING a_lambda
or for CODE
BIND my_string => ->STRING GETFUNCTION some_function TOK
Functions are created with
DEFINE sayhello => [ PRINTLN HELLO ]
They can be created directly with
FUNC [ ]
Throff keeps track of executable code by marking the data as executable, not the variable. Because CODE is an executable datatype, any variable holding CODE immediately becomes a function, like this:
a
BIND a => GETFUNCTION say_hello TOK
DEFINE say_hello => FUNC [ PRINTLN Hello ]
will output
Hello
This means that any attempt to use functions as arguments to other functions will explode in your face. For instance
MAP PRINTLN [ 1 2 3 4 ]
will print out
[ 1 2 3 4 ]
instead of
MAP [ PRINTLN ] [ 1 2 3 4 ]
1
2
3
4
If you want to pass functions around for higher-order programming, you will need LAMBDAs.
The easy way to do this is to wrap the function
MAP my_lambda [ 1 2 3 4 ]
BIND my_lambda = [ PRINT ]
You can call a CODE or a LAMBDA with CALL.
CALL my_lambda
CODEs can be converted to arrays:
BIND my_array => ARRAY GETFUNCTION some_function TOK
CODEs can be converted to strings:
BIND my_string => STRING GETFUNCTION some_function TOK
CODEs can be converted to LAMBDAs:
BIND my_lambda => LAMBDA GETFUNCTION PRINT TOK
CALL GETFUNCTION my_lambda TOK [ Hello ]
Hashes are kept as native hashes, and are created with NEWHASH. You can get values with GETHASH/HASHGET and set values with SETHASH/HASHSET.
You can get a list of the keys with KEYS.
The string representation of a throff hash is generated on the fly and not stored. The string representing the hash will be a series of throff commands that will rebuild the hash when EVALed. Key order is not guaranteed.
Handling nested hashes is currently too difficult, I will be redesigning this.
Wrappers manage native datastructures. There are no guarantees or guidelines for access to wrappers. Wrappers are typically returned by automatically generated code that provides access to lower-level functionality.
Wrappers usually won't have a string representation. If a wrapper is used as a string, throff will usually attempt to print the code that was used to create the wrapper, or just throw an error.
Since throff hashes use the string representation of the key, wrappers should not be used as hash keys, unless you are very sure that the string representation exists and is meaningful.
Bytes are a special type of wrapper, because Throff has some extra in functions for manipulating them. Throff will not move the bytes in memory, so pointers into the bytes will remain valid. However, you will need to make sure the bytes are not freed by the garbage collector by keeping a Throff binding to the bytes.
You can convert a throff value to bytes with ->BYTES, or map a file with MMAPFILE. You can get the length (in bytes) with LENGTH, read parts of the BYTES with GETBYTE, or set them with SETBYTE.
Note that the byte operations are not immutable. Modifying byte data is destructive. Most other Throff functions will make a copy of data before modifying it.
THIN converts a function into a THIN function, which has no private lexical environment - it shares its parents' lexical scope.
THIN functions are mostly used for forcing throff to act a bit more like an imperative language:
PRINTLN x
WHEN true [ REBIND x => 5 ]
BIND x => 1
will print "1"
PRINTLN x
WHEN true THIN [ REBIND x => 5 ]
BIND x => 1
will print "5"
MACRO converts a function into a MACRO. MACROs are functions with no lexical environment at all - they use the same environment as the caller (dynamic scope).
Macros are heavily used to provide new langauge features in throff. For instance, the WITH array FROM hash
is implemented with a macro. For many examples of macro use, look in throffbootstrap.go
Inserts hashkeys into the current namespace
PRINTLN a
WITH [ a b c ] FROM H[ a 1 b 2 c 3 ]H
Creates variables named in array, and fetches their values from hash. It loads the requested keys from the hash and puts them in the current environment.
Updating the variables will not update the hash nor vice versa. HASHes, like most other data types, are immutable, so any updates create a new data structure and the old values are not changed.
WITH [ headers cookies body ] FROM http_request
is equivalent to
DEFINE headers => GETHASH headers http_request
DEFINE cookies => GETHASH cookies http_request
DEFINE body => GETHASH body http_request
Calls function n times.
The function must take no arguments and return no values (i.e. it is called for its side effects)
REPEAT 10 [ p Hello World ; ]
FOREVER
Calls function inan infinite loop.
The function must take no arguments and return no values (i.e. it is called for its side effects)
FOREVER [ p Hello World ; ]
REPEAT
Starts a new thread to run function. A clone of the current interpreter is used for the the new thread. Due to Throff's immutable semantics, the new thread will not be able to update values in the old thread. However this protection does not work for anything external to the interpreter, like sockets, files or databases. If both the old and new threads attempt to write to the same file handle, or read from the same network socket, corruption will occur.
Threads cannot communicate directly with each other, you need to use something like a QUEUE.
The ACTOR functions provide a convenient way to run a thread and communicate with other threads.
- function - The function to run in the new thread. It must take no arguments and return no values
THREAD [ p Hello World ; ]
Defines a function name with body value
Note that the => is part of the syntax
- name - A variable name that will be bound in the current namespace.
- value - Any value or function
DEFINE creates a function. name does not need to be quoted, so long as you remember to put the => operator afterwards.
value must be a function definition (LAMBDA or CODE), and name will become a function.
nothing
BIND, REBIND, TOK, GETFUNCTION, ARG, CALL, ->LAMBDA
ARG binds a function argument to name. It is currently an alias to BIND.
Note that the => is part of the syntax
DEFINE greet => [
p Hello NAME ;
ARG NAME =>
]
greet [ Bob ]
> Hello Bob
See Also:
DEFINE, BIND, REBIND
Note that the => is part of the syntax
Variables are created with the BIND command.
PRINTLN x
BIND x => 10
> 10
Bindings are almost immutable - you can only modify bindings in your current scope. Read the Scoping section for more details.
Once you return from your current scope, the bindings are thrown away.
Overwrites an existing binding
REBIND x => 20
The variable must have been created already with BIND, before it can be rebound
Note that no matter what happens, the change is only visible inside the current (and lower) scope.
TOK (token) quotes the word to its left
PRINTLN TOK
Out of all the functions in Throff, TOK is the only function that can affect anything to the left. TOK is used to quote function and variable names, preventing them from being resolved to their values. => and = are aliased to TOK, and thus quote the variable name to their left. This is how you can assign to a variable, without the variable name being replaced with its value.
PRINTLN TOK
Stack top: PRINTLN
Instead of printing TOK, TOK pushes the TOKEN "PRINTLN" onto the stack.
While Throff is bootstrapping, TOK is used to quote names when they are being defined.
DEFINE TRUE TOK [ EQUAL 1 1 ]
TOK is useful when you want to retrieve a function value. If you just use the function name, it will activate. So if you try to rename PRINTLN
DEFINE printline => PRINTLN
> ERROR: read past end of stack
it will fail because PRINTLN will activate. To get the function behind PRINTLN
DEFINE printline => GETFUNCTION PRINTLN TOK
Note that => is just syntactic sugar for TOK, as are several other symbols I can't recall right now.
GETFUNCTION
Calls a function or a lambda, or activates a PROMISE.
Call activates a lambda function, which are built with [ ] or fetched with GETFUNCTION
Example:
CALL [ PRINTLN [ Hello World ] ]
CALL GETFUNCTION PRINTLN TOK [ Hello World ]
DEFINE myHello => [ p Hello my world ; ]
CALL myHello
See Also:
GETFUNCTION
Converts a CODE into a LAMBDA
FIXME move this discussion to another page
All Throff functions are of type CODE. Any variable containing a CODE becomes a function, and will activate any time the variable name appears. This makes function arguments difficult to deal with, because if someone passes your function a CODE, each time you refer to that CODE, you will need to write
GETFUNCTION argname TOK
this is too much typing and too easy to forget, so instead you should convert the CODEs to LAMBDAs. Lambdas are identical to CODE in every way, except that to activate them, you need to use CALL
Internally throff examines each word of the program in turn. If the word is of type CODE, throff activates it immediately. If the word is of type LAMBDA, throff pushes it onto the stack.
Example:
CALL myHello
See Also:
GETFUNCTION
Converts anything into a STRING.
In Throff, almost everything has a string representation. The only exceptions are WRAPPERs around internal data structures, and even then, these will have some kind of descriptive string. ->STRING will build the string representation of a data structure recursively, so calling it on a HASH or ARRAY might result in a very large string.
Converts a CODE or LAMBDA into an ARRAY
CODEs, LAMBDAs and ARRAYs use the same internal data structure, and so can be used almost interchangably. The only difference is how the interpreter treats them at certain times, and CODE/LAMBDAs have a lexical environment.
MAP [ PRINTLN ] ->ARRAY [ one two three four ]
one two three four
Discards everything to the right of the decimal point
Adds two numbers
ADD 2 3
5
Subtract two numbers
SUB 5 3
2
Multiplies two numbers
MULT 3 4
12
Divides two numbers
DIVIDE 10 9
1.11111
Returns a modulus b
MODULUS 100000 3
1
Natural logarithm
LN 7
2.807
Sine (radiians)
SIN DIVIDE 3.1415927 3
0.8660
Gets the nth letter of astring. The returned value is a one letter string, because there is no character type in Throff.
Sets the nth letter of string, returns a new string containing the changes. The old string is untouched.
Extracts and returns a substring of string, from start to end. Note that the returned string might still be part of the original string.
Returns a new string, which is s1 with s2 appended
Combines array into a single string, with separator placed between each element of array.
Returns a new string, which is all the elements of array concatenated together.
Returns a new string, which is made of all the elements of array with string in between each element
Example
STRING-JOIN , A[ HELLO WORLD ]A
> HELLO,WORLD
Pluralises string by adding an s to the end if number is not equal to one.
Example
PRINLN A[ 99 PLURAL 99 bottle of beer ]A
> 99 bottles of beer
Evaluates string inside environment. You can get an environment from almost any throff type (except macros) using ENVIRONMENTOF.
You can get the current environment with
ENVIRONMENTOF [ THIS ]
Bytes provide direct memory access. Unlike everything else in Throff, they are mutable by default. They are also a good way to to corrupt memory and cause throff to crash.
Converts any throff data into a BYTES. If the input is a WRAPPER, then it will be used unchanged (and without being copied into a new memory location, which is what the other type converters should do). Any data other than WRAPPERs will be converted to a STRING, and then that STRING will be used as BYTES.
Gets the byte at position from bytes.
A string, containing the ASCII representation of the byte.
FIXME should this be returning a string?
FIXME implement this
Note that this function breaks Throffs immutable data structures guarantee. Throff is, ultimately, a practical language for doing things in, and we need to be able to modify bytes in MMAPed files, and other situations. Without making it a fully functional language, the best we can do is recommend that you only modify BYTES that you are sure that no-one else has a reference to. i.e. If you create them yourself, in the same subroutine.
Note that this still breaks some throff features - you will still be able to rewind your program, but when you do, the bytes you modified will not change back.
FIXME implement this
Create an empty new array
Note: You can also use the literal
A[ ]A
Pushes item onto the end of array. Returns the new array
Example
REBIND myArray => ARRAYPUSH myArray [ hello ]
Pops an item off the end of the array
Returns item - the popped item array - the new array, missing the final item
Example
REBIND myArray => REBIND dataItem => POPARRAY myArray
Description
POPARRAY returns two values: the item from the end of the array, and a new array, missing the final item. The original array is not affected!
As for POPARRAY, but the other end
As for pusharray, but the other end
Returns the item in array at position index
Example
BIND 3rdItem => GETARRAY 2 myArray
Returns true if array has no elements.
Returns a reversed copy of **array**
Returns the first element of array
FIXME: use this only for lists
FIXME: use this only for lists
Returns
- a copy of array1, with the first element removed
Returns a new array3 which is array1 with array2 appended to the end.
>> APPEND A[ hello ]A A[ world ]A
A[ hello world ]A
- A newly allocated array, combining array1 and array2
Create a new hash
Note: you can also use the literal
H[ ]H
H[ key value key value]H
- A new, empty hash
Copies hashtable and adds an entry for key to value. In the future this will use a persistent data structure. For now, ouch.
>> HASHSET H[ ]H greetings => [ hello world ]
H[ greetings [ hello world ] ]H
- a new hash. The old hash is unmodified
Copies hashtable and adds an entry for key to value. In the future this will use a persistent data structure. Note the argument order is changed.
>> SETHASH greetings => [ hello world ] H[ ]H
H[ greetings [ hello world ] ]H
- a new hash. The old hash is unmodified
KEYS H[ greetings [ hello world ] ]H
A[ greetings ]A
- array: the keys of the hash as an array
Returns the values of hashtable as an array
VALUES H[ greetings [ hello world ] ]H
A[ [ hello world ] ]A
- array: The values of the hash, as an array
array - The keys and values "flattened" into an array
>> KEYVALS H[ A 1 B 2 C 3 ]H
A[ A 1 B 2 C 3 ]A
Returns array KEYS hash array VALUES hash
Removes key from the hash
Queues are thread safe FIFO queues, most useful for sending messages between threads. All throff data types can be send through a queue, and since this will simply send a pointer, it is quick and efficient. Note that because everything in Throff is immutable, you can safely send data to as many threads as you want, and never have any problems with simulataneous updates or locks or whatever.
Unless you use SETBYTE. Then anything can go wrong.
Create a new thread-safe FIFO queue
Sends value to queue
Reads a value from queue
value - a single element from the queue
Fetch webpage
Returns string - webpage
Lookup hostname in the DNS system
Returns array - IP addresses
Lookup cname in the DNS system
Returns string - canonical DNS name
Lookup text records for given hostname in the DNS system
Returns array - list of text records
Lookup IP in the DNS system
Returns array - list of hostnames for given IP address
Prints value to STDOUT
Prints value to STDOUT, followed by a newline.
The condition value must be TRUE or FALSE. TRUE and FALSE are values returned by EQUAL, LESSTHAN, NOT, etc. See the CONDITIONALS section for more
IF [ LESSTHAN X 0 ]
[ PRiNTLN [ X is less than 0 ] ]
[ PRINTLN [ X is greater than or equal to 0 ] ]
but because IF is an expression, we can write
PRINTLN IF [ LESSTHAN X 0 ]
[ X is less than 0 ]
[ X is greater than or equal to 0 ]
Just like IF, but only has a true branch
WHEN TRUE [ PRINTLN [ hello world ] ]
Throff IF functions only accept TRUE and FALSE values, as returned by the TRUE and FALSE functions, or other built-ins listed below. Anything else is an error.
In order to match the convenience of other, more popular languages, Throff offers the TRUTHY function, which attempts to guess whether any value you give it is TRUE or FALSE.
Returns true if the string value of x is equal to the string value of y.
Warning: Don't use EQUAL on WRAPPERs like file handles, network sockets, database handles, etc. These things usually do not have string representations, so they will all "be equal", when in fact they are different.
Returns true if x is less than y
Returns TRUE if array has no elements.
IF only accepts TRUE or FALSE values, but other languages have more convenient IF statements that accept any value. TRUTHY provides this service for THROFF. Positive numbers are true, zero and negatives are false, and strings with length greater than 0 are true.
Exactly like IF, except it uses TRUTHY to decide whether value is TRUE or FALSE
Much neater than multiple IF statements, CASE provides a compact way to do multiple tests, in order.
CASE A[
LESSTHAN 0 X ... [ PRINTLN [ X IS GREATER THAN 0 ] ]
LESSTHAN X 0 ... [ PRINTLN [ X IS LESS THAN 0 ] ]
DEFAULT ... [ PRINTLN [ X IS EQUAL TO 0 ] ]
]A
Case tests each condition (on the left). If that condition is true, it calls the function on the right. CASE is an expression, the result of the function becomes the result of the CASE.
REBIND COUNT => ADD COUNT CASE A[
LESSTHAN 0 X ... -1
LESSTHAN X 0 ... 1
DEFAULT ... 0
]A
You can provide a function or a value, CASE will use CALL to resolve everything.
REBIND COUNT ADD COUNT CASE A[
[ LESSTHAN 0 X ] ... [ -1 ]
[ LESSTHAN X 0 ] ... [ 1 ]
[ DEFAULT ] ... [ 0 ]
]A
CATCH calls function. If function THROWs an error, thene the error handler will be run. error hander must take one argument, which will be the THROWn error message
Returns
The result of function, or the result of the error handler
See Also
THROW
THROW causes an error condition, which will be caught by the previously declared error handler, as declared by CATCH
message can be any value
THROW does not return
See Also CATCH
Call lambda with the Current Continuation. lambda must take one argument
- Nothing: CALL/CC never returns
Activate continuation with value. Control will jump to the place where the continuation was defined.
Returns
- Nothing: ACTIVATE/CC never returns
A PROMISE is a function that delays its execution until needed. It's a way to get some of the benefits of a lazy language without actually having a lazy language.
lambda must return one value, and take no inputs.
When a promise is created, it delays the execution of lambda until the first time that the promise is accessed - usually via its variable name. e.g.
PRINTLN GREETING
BIND GREETING => PROMISE [ HELLO ]
Promises are most useful when they are used on code that is expensive to run, like database or network calls. So for instance, instead of loading data from the database for all employees and putting it into an array, you can fill the array with PROMISES which will delay fetching the data until accessed.
After the lambda runs, its return value is cached, and the lambda is not called again. lambda is only ever called once.
Example
MAP [ PROMISE [ database-fetch USER ] ARG USER => ] [ BOB MARY SUE DAVE ]
Notes about "forcing" promises
Promises can be triggered accidentally by passing them to a function that accesses them. For instance, calling MAP on an array of PROMISEs will activate every PROMISE in the array, because MAP takes each element from the input array and "accesses" it. Other array functions like FOLD, FILTER, etc will do the same.
While promises are difficult to handle without accidentally triggering the code, sometimes it is possible to forget to trigger the code, resulting in printing out the promise, rather than its result. The easiest way to handle promises is to use ->LAMBDA to convert it to a normal LAMBDA, and then use CALL to fetch the result when you know you want it. You can also safely handle promises by putting them in ARRAYs or HASHes.
Because a promise is just a function, it has to be used like a function. If you never assign it to a variable and then use it, or use CALL on it, then the function won't run. e.g.
PRINTLN PROMISE [ HELLO ]
does not print HELLO, it prints [ FUNCTION DEFINITION ]. You actually need
PRINTLN CALL PROMISE [ HELLO ]
- A PROMISE.
Prints out the current data stack in an (almost) human readable format.
Trace on. Prints out every function as it is executed
Trace off. Stops printing out program flow.
Note to self: having a trace level setting would be very useful
Converts the internal data structure into a string. Not useful unless you are looking at the interpreter.
Actors are objects that run in their own thread. Actors receive commands via input queues, and return results over an output queue. Actors are asynchronous, and are best when used for slow-running code that can run in the background.
A good use of actors is networking code, e.g. fetching webpages, or communicating with a database. They can also be used as mutexes, because they will only process one command at a time. This makes them ideal for managing updates to databases and network services. Each command will run in the background, one after the other.
Actors should not be used for small, fast code e.g. numerical code. Do not, for instance, make an actor that squares a number and returns it.
Each message to an actor requires several rounds of locking and hash accesses, so it works better when wrapping slower code.
Create a new actor. lambda will be called for each message sent to the actor. lambda must take one argument and return one value, which will be written to the output queue.
Actors run in their own thread so they are a great place to put code that will block or run slowly.
Returns
- An actor. You can send messages to it with CALLA
See Also
CALLA
CALLA sends a value to an actor. Value can be anything e.g. array, hash, wrapper, etc.
CALLA returns a resultFunction that takes no args but will return the correct value when it has been calculated. See the section on PROMISEs for more details.
Calling the resultFunction will try to fetch the return value from the actor. If the actor has not finished yet, then the current thread will block until the actor finishes.
The return value is cached and further accesses will be instant.
PRINTLN VALUE
BIND VALUE => CALLA DOUBLE 2
BIND DOUBLE => ACTOR [ MULT 2 ]
- a PROMISE. This is (or will be), the return value from the actor
See Also
ACTOR, PROMISE
Throff supports starting subprocesses (other programs), and the ability to wait for or kill them. Functions to capture the STDOUT/STDERR coming soon.
Start another program at path. The args list will be passed to the program as its argument list. Note that the first element of args will be the $0 arg for the program, which is usually set to the path by other languages. You can set anything you want there, but some programs might rely on it being the path to the program. You must provide something for the 0th arg, as all programs start reading the arg list from the 1st place. e.g.
STARTPROCESS /bin/echo [ /bin/echo Hello world ]
If you omit the second /bin/echo, echo will only print "world"
- ProcHandle A native wrapper around a process handle. Used by KILLPROC and WAITPROC.
Immediately kill the process represented by ProcHandle.
- code A success code.
Wait for the subprocess ProcHandle to finish before continuing.
- Summary A summary of the process' execution, e.g. time, exit code, etc
Runs command line in the default shell, and returns the output.
This command works on Mac, Linux and windows, but you get whatever default shell is installed, so your commands will not be cross platform.
Starts a subprocess and waits for it to finish, then returns all the programs output as a string. Output from STDOUT and STDERR will be mixed together in the same way it would be printed to a screen.
executable must be the full path to a program. The rest of the array will be used as program arguments
- Output All the program output
OS returns the name of the operating system that throff was compiled for.
Map is the usual as found in functional languages - it calls function on each element of the input array, and builds an array of the results.
Use ITERATE if you do not plan to use the results, it consumes less memory.
Fold works like map, it applies a function to every element of the array. But instead of building a new array, FOLD passes the result of your function as input to the next step.
FOLD [ MULT elem accum ] start array
Here FOLD will multiply start by the first element of your array, then takes the results of that (the accumulator) and multiplies it with the second element of your array, and so on.
function
The function must work like this:
FUNC element accumulator -》 value
As for fold, but works on any data structure. It recursively visits every element of every subtree until it has called function on every element in the tree.
function
The function must work like this:
FUNC element accumulator -》 value
As for MAP, but works on any data structure. It recursively visits every element of every subtree until it has called function on every element in the tree.
function
The function must work like this:
FUNC element -》 value
As for ITERATE, but works on any data structure. It recursively visits every element of every subtree until it has called function on every element in the tree.
function
The function must work like this:
FUNC element
Namespaces and scopes are still a bit wobbly in Throff, but are at the point where they function well enough to be called 'feature complete'.
Manipulating them directly is usually safe, because they are immutable. Or mostly immutable.
Scopes are all nested, with the root scope being the initial scope that the program starts with. Variables can be looked up with GETLEX, which will search every scope from the current up to the root.
The current scope is mutable but all other scopes (especially parent) are immutable, although it is possible to find a way to modify the memory of another scope. Doing this will not work the way you want it to, since scopes are often copied or optimised away.
Almost every [ ] introduces a new scope (MACRO and THIN functions are exceptions). This means you can't update variables inside a MAP or FOLD or FOR loop, because all the variables inside the [ ] are discarded when you leave the [ ].
BIND creates a new variable in the current scope, and fails if the variable already exists. REBIND shadows previous bind, usually a variable in the parent scope. Note that REBINDing a variable can only update the value for future code. Every REBINDing is actually a completey new variable definition. And so it can't be seen by any code that has already been definined.
Throff will throw an error if you attempt to bind a variable name that is already bound in a parent scope.
There are two special types of scoping rules: THIN and MACRO.
MACROs have no scope and no environment when they are defined, instead they temporarily use the environment where they are executed, enabling them to create and destroy variables in other scopes. MACROs are heavily used to implement language features, and should also be quite fast (since we don't have to create a scope when they run).
THIN functions are probably a bad idea. THIN functions use their parent scope, and not their own, when they run. This does allow you to update variables from inside a FOR loop, but note that the usual rules apply. The updated bindings are only visible to future code.
Sets the variable name to value. name must already exist as a variable. Better to use BIND (TODO delete SETLEX?)
Deletes variable name. name must already exist.
Looks up the value of name using the current scope.
Creates binding name and sets its value to value. Fails if name alredy exists. This is the fundamental way to define functions and variables. But it lacks any type or error checking, so it is better to use DEFINE or BIND.
Throff thinks that everything is a function, even numbers and strings - they are just functions that return themselves. If you have a function called name, then any time name appears in the program, it will automatically activate. This can cause some nasty bugs, for example:
DEFINE print_code => [
p Code is aFunc ;
ARG aFunc =>
]
if you use print_code on a function, you will get, at best, a crash
print_code ADD
> ERROR: read on empty stack
Instead of printing out ADD, the program crashes because the variable aFunc became a function that tries to add the next two arguments, in this case, the letter ';' and the top of the stack.
In this example, the problem is obvious. However if the function is stored in a data structure like an array or hash, then there is no reasonable way to check that a value is a function before it activates.
To avoid this mishap, you will need to typecast the arguments to your function, which is verbose and not always appropriate:
ARG aFunc => ->LAMBDA
or
ARG aFunc => ->STRING
or
ARG aFunc => ->ARRAY
or you can use GETFUNCTION to safely get the value of aFunc
p Code is GETFUNCTION aFunc TOK ;