F is a pure functional concatenative language originally designed as an extension of False. F contains the list-operations of K3 and the dip combinator of Joy. Floating-point and symbolic datatypes are supported. One-time assignment is enforced in syntax. A theory of function-valence and -charge is outlined. F also contains a general continuation primitive $, and the pattern sublanguage of XY. G is a variant of F in which the K3 adverbs are implemented as primitives.
F has the following properties:
The language is concatenative The language is purely functional All K verbs are implemented All primitives are denoted by single symbols Primitive symbols are as mnemonic as possible
The language is concatenative. F tokens are words, words denote functions, and the concatenation of words denotes the composition of functions. In classical concatenative languages, everything is a function from stacks to stacks. In F, everything is a function from triples of (environment;stack;queue) to triples of (environment;stack;queue).
The language is purely functional. There are no side-effects. F has assignment, but not reassignment. This means that you can't use a variable to store dynamic state. F assignment associates names with values in an environment which is passed as an argument and returned as a value. F also has commands for interacting with the run-time environment and the file-system, but these operations are notationally differentiated from the operators of F: "h", "r", &c. They are intended as debugging aids only.
All K verbs are implemented. Some K verbs are implemented as primitives, and some are derived in the F prelude. For example, the atom primitive @ of K is defined as [#ints~]; i.e. shape matches the empty integer vector. Where K provides a pair of functions, one of which is easily defined in terms of the other, F implements one as a primitive and derives the other. For example, and is primitive (&) and or is derived. The criterion for dividing related pairs is simply this: the derived definition must not be egregiously inefficient when compared to the primitive it supplants.
All primitives are denoted by single symbols. Although list-notation ([x y z]) is supported, any list can be constructed functionally with ' (quote) and , (join).
Primitive symbols are as mnemonic as possible. There are five ways the mapping of a function to a symbol can be mnemonic:
The symbol is in common use for the mapped function (e.g. + for addition) The symbol is mapped to that function in K (e.g. ? for find) or False (e.g. ! for unquote) The name of the symbol is a homonym for the mapped function (e.g. ' for quote) A pair of related functions (inverses, or near-inverses) are mapped to a pair of related symbols (e.g. / and \ for take and drop) Where several K primitives are mapped to one symbol, the primitives should form an easily remembered group based on some common property; e.g. both upgrade and enum return indices based on an ascending relation, so both are mapped to <.
The initial state of the interpreter consists of an environment containing the F words of the prelude, an empty result stack, and a string (character-vector) to be evaluated. The input string is tokenized and parsed to obtain the initial queue.
The input queue is a K list, possibly containing integers, floats, symbols, null, functions, and lists ("quotations"). The result stack is initially empty. The environment is a K dictionary. F processes the environment, stack, and queue repeatedly until the queue is empty.
If the first item on the queue is an integer, float, null, the prototype symbol `, or a list, the item is pushed onto the stack.
If the first item is an undefined symbol, then if it's a shuffle it's applied; otherwise, a variable is created (in the environment) having the top of the stack as the value.
If the first item is a defined symbol, its value is retrieved (from the environment) and pushed onto the stack.
If the first item is a function, then it is applied to the environment, stack, and queue to produce a new environment, stack, and queue.
Observe that the domain of the result stack is a proper subset of the domain of the input queue. On the queue we may find character-atoms, such as "r", and strings, such as "blah". But character-atoms are executed away when they are evaluated, and no F primitive ever produces one, and strings are comments, which are not processed.
The trace command displays the stack and queue for selected objects in the trace list T:
F>[fac] "t"
F>3 fac! 3 ♦ fac ! 3 2 ♦ fac ! * 3 2 1 ♦ fac ! * * 6 F>
F>[fac cond] "t"
F>3 fac! 3 ♦ fac ! 3 [1 =] [] [dup ! pred ! fac ! *] ♦ cond ! 3 2 ♦ fac ! * 3 2 [1 =] [] [dup ! pred ! fac ! *] ♦ cond ! * 3 2 1 ♦ fac ! * * 3 2 1 [1 =] [] [dup ! pred ! fac ! *] ♦ cond ! * * 6 F>
F>[] "t"
F>3 fac! 6