Speakeasy (computational environment)

Speakeasy was a numerical computing interactive environment also featuring an interpreted programming language. It was initially developed for internal use at the Physics Division of Argonne National Laboratory by the theoretical physicist Stanley Cohen. He eventually founded Speakeasy Computing Corporation to make the program available commercially.
Speakeasy is a very long-lasting numerical package. In fact, the original version of the environment was built around a core dynamic data repository called "Named storage" developed in the early 1960s, while the most recent version has been released in 2006.
Speakeasy was aimed to make the computational work of the physicists at the Argonne National Laboratory easier. It was initially conceived to work on mainframes (the only kind of computers at that time), and was subsequently ported to new platforms (minicomputers, personal computers) as they became available. The porting of the same code on different platforms was made easier by using Mortran metalanguage macros to face systems dependencies and compilers deficiencies and differences. Speakeasy is currently available on several platforms: PCs running Windows, macOS, Linux, departmental computers and workstations running several flavors of Linux, AIX or Solaris.
Speakeasy was also among the first interactive numerical computing environments, having been implemented in such a way on a CDC 3600 system, and later on IBM TSO machines as one was in beta-testing at the Argonne National Laboratory at the time.
Almost since the beginning (as the dynamic linking functionality was made available in the operating systems) Speakeasy features the capability of expanding its operational vocabulary using separated modules, dynamically linked to the core processor as they are needed. For that reason such modules were called "linkules" (LINKable-modULES). They are functions with a generalized interface, which can be written in FORTRAN or in C.
The independence of each of the new modules from the others and from the main processor is of great help in improving the system, especially it was in the old days.
This easy way of expanding the functionalities of the main processor was often exploited by the users to develop their own specialized packages. Besides the programs, functions and subroutines the user can write in the Speakeasy's own interpreted language, linkules add functionalities carried out with the typical performances of compiled programs.
Among the packages developed by the users, one of the most important is "Modeleasy", originally developed as "FEDeasy" in the early 1970s at the research department of the Federal Reserve Board of Governors in Washington D.C..
Modeleasy implements special objects and functions for large econometric models estimation and simulation.
Its evolution led eventually to its distribution as an independent product.
Syntax.
The symbol :_ (colon+underscore) is both the Speakeasy logo and the prompt of the interactive session.
The dollar sign is used for delimiting comments; the ampersand is used to continue a statement on the following physical line, in which case the prompt becomes :& (colon+ampersand); a semicolon can separate statements written on the same physical line.
 $ suppose you have a very long statement, 
 $ you can write it on multiple physical lines using "&" 
 $ at the end of the line to be continued:
 :_ the_return_value = this_is_a_function_with_many_arguments(argument_1, argument_2, &
 :& argument_3, argument_4, argument_5, argument_6)
 $ on the other hand, you can collect several short statements 
 $ on a single physical line using ";"
 :_ a=1; b=2; c=3; d=4 
As its own name tells, Speakeasy was aimed to expose a syntax as friendly as possible to the user, and as close as possible to the spoken language. The best example of that is given by the set of commands for reading/writing data from/to the permanent storage. E.g. (the languages keywords are in upper case to clarify the point):
 :_ GET my_data FROM LIBRARY my_project
 :_ KEEP my_data AS a_new_name_for_mydata IN LIBRARY other_project 
Variables (i.e. Speakeasy objects) are given a name up to 255 character long, when LONGNAME option is ON, up to 8 characters otherwise (for backward compatibility). They are dynamically typed, depending on the value assigned to them.
 :_ a=1
 :_ whatis a
 A is a REAL SCALAR.
 :_ a="now a character array"
 :_ whatis a
 A is a 21 element CHARACTER ARRAY.
Arguments of functions are usually not required to be surrounded by parenthesis or separated by commas, provided that the context remains clear and unambiguous. For example:
 :_ sin(grid(-pi,pi,pi/32)) $ fully specified syntax
can be written :
 :_ sin grid(-pi,pi,pi/32) $ the argument of function sin is not surrounded by parenthesis
or even 
 :_ sin grid(-pi pi pi/32) $ the arguments of function grid can be separated by spaces
Many other syntax simplifications are possible; for example, to define an object named 'a' valued to a ten-elements array of zeroes, one can write any of the following statements:
 :_ a=array(10:0,0,0,0,0,0,0,0,0,0)
 :_ a=0,0,0,0,0,0,0,0,0,0
 :_ a=0 0 0 0 0 0 0 0 0 0
 :_ a=ints(10)*0
 :_ a=10:
Speakeasy is a vector-oriented language: giving a structured argument to a function of a scalar, the result is usually an object with the same structure of the argument, in which each element is the result of the function applied to the corresponding element of the argument. In the example given above, the result of function sin applied to the array (let us call it x) generated by the function grid is the array answer whose element answer(i) equals sin(x(i)) for each i from 1 to noels(x) (the number of elements of x). In other words, the statement
 :_ a=sin(grid(-pi pi pi/32))
is equivalent to the following fragment of program: 
 x=grid(-pi pi pi/32) $ generates an array of real numbers from -pi to pi, stepping by pi/32
 for i = 1,noels(x) $ loops on the elements of x
 a(i) = sin(x(i)) $ evaluates the i-th element of a
 next i $ increment the loop index
The vector-oriented statements avoid writing programs for such loops and are much faster than them.
Work area and objects.
By the very first statement of the session, the user can define the size of the "named storage" (or "work area", or "allocator"), which is allocated once and for all at the beginning of the session. Within this fixed-size work area, the Speakeasy processor dynamically creates and destroys the work objects as needed. A user-tunable garbage collection mechanism is provided to maximize the size of the free block in the work area, packing the defined objects in the low end or in the high end of the allocator. At any time, the user can ask about used or remaining space in the work area.
 :_ SIZE 100M $ very first statement: the work area will be 100MB
 :_ SIZE $ returns the size of the work area in the current session
 :_ SPACELEFT $ returns the amount of data storage space currently unused
 :_ SPACENOW $ returns the amount of data storage space currently used
 :_ SPACEPEAK $ returns the maximum amount of data storage space used in the current session
Raw object orientation.
Within reasonable conformity and compatibility constraints, the Speakeasy objects can be operated on using the same algebraic syntax.
From this point of view, and considering the dynamic and structured nature of the data held in the "named storage", it is possible to say that Speakeasy since the beginning implemented a very raw form of operator overloading, and a pragmatic approach to some features of what was later called "Object Oriented Programming", although it did not evolve further in that direction.
The object families.
Speakeasy provides a bunch of predefined "families" of data objects: scalars, arrays (up to 15 dimensions), matrices, sets, time series.
The elemental data can be of kind real (8-bytes), complex (2x8-bytes), character-literal or name-literal ( matrices elements can be real or complex, time series values can only be real ).
Missing values.
For time series processing, five types of missing values are provided. They are denoted by N.A. (not available), N.C. (not computable), N.D. (not defined), along with N.B. and N.E. the meaning of which is not predetermined and is left available for the linkules developer. They are internally represented by specific (and very small) numeric values, acting as codes.
All the time series operations take care of the presence of missing values, propagating them appropriately in the results.
Depending on a specific setting, missing values can be represented by the above notation, by a question mark symbol, or a blank (useful in tables). When used in input the question mark is interpreted as an N.A. missing value.
 :_ b=timeseries(1,2,3,4 : 2010 1 4)
 :_ b
 B (A Time Series with 4 Components)
 1 2 3 4
 :_ b(2010 3) = ? 
 :_ showmval qmark
 :_ b
 B (A Time Series with 4 Components)
 1 2 ? 4
 :_ 1/b
 1/B (A Time Series with 4 Components)
 1 .5 ? .25
 :_ showmval explain
 :_ b
 B (A Time Series with 4 Components)
 1 2 N.A. 4
 :_ 1/b
 1/B (A Time Series with 4 Components)
 1 .5 N.C. .25
In numerical objects other than time series, the concept of "missing values" is meaningless, and the numerical operations on them use the actual numeric values regardless they correspond to "missing values codes" or not (although "missing values codes" can be input and shown as such).
 :_ 1+?
 1+? = 1.00
 :_ 1/?
 1/? = 5.3033E36
 :_ 1*?
 1*? = ?
Note that, in other contexts, a question mark may have a different meaning: for example, when used as the first (and possibly only) character of a command line, it means the request to show more pieces of a long error message (which ends with a "+" symbol).
 :_ a=array(10000,10000:)
 ARRAY(10000,10000:) In line "A=ARRAY(10000,10000:)" Too much data.+
 :_ ?
 Allocator size must be at least 859387 kilobytes.+
 :_ ?
 Use FREE to remove no longer needed data
 or
 use CHECKPOINT to save allocator for later restart.+
 :_ ?
 Use NAMES to see presently defined names.
 Use SIZE & RESTORE to restart with a larger allocator.
 :_ ?
 NO MORE INFORMATION AVAILABLE.
Logical values.
Some support is provided for logical values, relational operators (the Fortran syntax can be used) and logical expressions.
Logical values are stored actually as numeric values: with 0 meaning false and non-zero (1 on output) meaning true.
 :_ a = 1 2 3 4 5
 :_ b = 1 3 2 5 4
 :_ a>b
 A>B (A 5 Component Array)
 0 0 1 0 1
 :_ a<=b
 A<=B (A 5 Component Array)
 1 1 0 1 0
 :_ a.eq.b
 A.EQ.B (A 5 Component Array)
 1 0 0 0 0
 :_ logical(2) $ this changes the way logical values are shown
 :_ a>b; a<=b; a.eq.b
 A>B (A 5 Component Array)
 F F T F T
 A<=B (A 5 Component Array)
 T T F T F
 A.EQ.B (A 5 Component Array)
 T F F F F
Programming.
Special objects such as "PROGRAM", "SUBROUTINE" and "FUNCTION" objects (collectively referred to as "procedures") can be defined for operations automation. Another way for running several instructions with a single command is to store them into a use-file and make the processor read them by mean of the USE command.
Use-files.
"USEing" a use-file is the simplest way for performing several instruction with minimal typed input. (This operation roughly corresponds to what "source-ing" a file is in other scripting languages.)
A use-file is an alternate input source to the standard console and can contain all the commands a user can input by the keyboard (hence no multi-line flow control construct is allowed). The processor reads and executes use-files one line at a time.
Use-file execution can be concatenated but not nested, i.e. the control does not return to the caller at the completion of the called use-file.
Procedures.
Full programming capability is achieved using "procedures". They are actually Speakeasy objects, which must be defined in the work area to be executed. An option is available in order to make the procedures being automatically retrieved and loaded from the external storage as they are needed.
Procedures can contain any of the execution flow control constructs available in the Speakeasy programming language.
Programs.
A program can be run simply invoking its name or using it as the argument of the command EXECUTE. In the latter case, a further argument can identify a label from which the execution will begin.
Speakeasy programs differs from the other procedures for being executed at the same scoping "level" they are referenced to, hence they have full visibility of all the objects defined at that level, and all the objects created during their execution will be left there for subsequent uses. For that reason no argument list is needed.
Subroutines and functions.
Subroutines and Functions are executed at a new scoping level, which is removed when they finish. The communication with the calling scoping level is carried out through the argument list (in both directions). This implements data hiding, i.e. objects created within a Subroutine or a Function are not visible to other Subroutine and Functions but through argument lists.
A global level is available for storing object which must be visible from within any procedure, e.g. the procedures themselves.
The Functions differ from the Subroutines because they also return a functional value; reference to them can be part of more complex statement and are replaced by the returned functional value when evaluating the statement.
In some extent, Speakeasy Subroutines and Functions are very similar to the Fortran procedures of the same name.
Flow control.
An IF-THEN-ELSE construct is available for conditional execution and two forms of FOR-NEXT construct are provided for looping.
A "GO TO "label"" statement is provided for jumping, while a Fortran-like computed GO TO statement can be used fort multiple branching.
An ON ERROR mechanism, with several options, provides a means for error handling.
Linkule writing.
Linkules are functions usually written in Fortran (or, unsupportedly, in C). With the aid of Mortran or C macros and an API library, they can interface the Speakeasy workarea for retrieving, defining, manipulating any Speakeasy object.
Most of the Speakeasy operational vocabulary is implemented via linkules. They can be statically linked to the core engine, or dynamically loaded as they are needed, provided they are properly compiled as shared objects (unix) or dll (windows).