Printable Version of this PageHome PageRecent ChangesSearchSign In

From Objects to L Functions

Table of Contents

  1. Objects are Really Just Functions
  2. Records, Blocks, Tuples & Integers
  3. Internal Structure
  4. Smalltalk Objects as L Functions
  5. E Objects as L Functions
  6. Lexical Environment
  7. Thread Environment
  8. Parallel & Sequential Computation
  9. Meta Interface
  10. Currying
  11. No VM, Just Kernel Functions
  12. Abstract Syntax
  13. L-Expression Concrete Syntax

Objects are Really Just Map Functions

In curried form, an object is a map from selector to:
value (if simple accessor),
action result (if unary method), or
block (if arguments expected).

Classes and inheritance are a way to share maps, but then instance-specific state has to be supply as an extra argument to blocks in the shared map.

L provides the mechanisms of objects (maps and sharing of maps) using functions/closures, rather than objects, classes, inheritance, etc, directly.

Four Kinds of L Values (all Functions)

Record - consists of a map and a default function. The map associates keys to values. Keys and values may be any L value.
Applying a record to a key returns the map's associated value, or if the key is not found, applies the default function to the receiver and args (rargs).

Block - consists of an environment and code.
Applying a block to arguments executes its code against the rargs and its environment.

Tuple - consists of an array of values.
Applying a tuple to an integer index returns the value at that index, or Undefined, if the index is out of range.
Applying a tuple to a non-integer key returns the result of applying the TupleFunctions record to the key and tuple (rargs).

SmallInteger - a specially tagged reference whose bits encode the integer value (a la Smalltalk).
Applying a smallInteger to a key returns the result of applying the SmallIntegerFunctions record to the key and smallInteger (rargs).

Internal Structure

Records, blocks, and tuples all have the same N+2 structure
{N, class, value1, ..., valueN}.

A class is a tuple {N, class, code, meta, ...}.

A value is a smallInteger or a tagged pointer to a structure. The tag indicates whether to treat the structure as a tuple or an executable (block or record). If executable then the structure's class code is executed against the rargs upon application.

A block class looks like {2, class, code, meta}. The code jumps to code in the instance or executes directly.

A record class looks like {4, class, code, meta, selectors, default}. The code looks up the index of the supplied selector in selectors returning the instance's value at that index, or if selector not found, applies default to rargs.

So every structure has two views: tuple and executable. This is useful for privileged code like the garbage collector (to view all functions as tuples). Changing the view of a structure requires the right capability.

Smalltalk Objects as L Functions

A Smalltalk class is an L record mapping method selectors to blocks. Each block shares a class environment holding class variables and instVar names.

A Smalltalk instance is an L block containing instVar values and a Smalltalk class. Its forwards incoming messages to the class adding itself as an extra argument.

In each class method, the self parameter is bound to the extra argument. InstVar references are translated to index accesses to self as a tuple.

Each Smalltalk class record has a superclass record as its default function. The top of the superclass hierarchy is a block that sends doesNotUnderstand to self.

Each class environment has its class's superclass's environment as its default (inheriting super class variables and instVar names).

E Objects as L Functions

An E vTable is an L record mapping method selectors to methods (blocks with no environment).

An E object is an L block containing the lexical environment it was created in and a vTable. It forwards incoming messages to the vTable adding the lexical environment as an extra argument.

vTable methods look up free variables in the extra argument.

A vTable's default function is its match routine, which defaults to raising a doesNotUnderstand exception if not specified.

Lexical Environment

A lexical environment is a record from names to values with the outer lexical environment as the default.

Names (keys) can be any value.

"Here" returns the current lexical environment.

"Here x" returns the value bound to key x, where x is any expression.

If x is a literal symbol, then the symbol name can be used instead.
Eg. "Here 'foo" is equivalent to "foo".

Thread Environment

A thread environment, like a lexical environment, is a map from keys to values.

Every function call and return (continuation call) has a thread environment as an extra implicit argument.

The implicit thread returned by a function application is automatically supplied as the implicit thread argument to the next function application.

"Current" returns the thread implicitly supplied to it.

"Change x y" implicitly returns a child of the thread implicitly supplied to it with x bound to y (shadowing any previous x binding).

Threads makes state change explicit (L is purely functional).

Threads isolate changes from one another (ie. transactions).

Threads can be merged by combining all bindings since the common parent into one thread. The merge fails if two threads bound the same key.

Parallel and Sequential Computation

"x ; y" means compute x before y, supplying the result thread of x to y.

"x , y" means compute x and y independently, supplying the same current thread to each. Then supply the merge of the result threads to the next sequential computation (or return the merged thread if last). Also, if last, return the explict results in a tuple.

"," has precedence over ";". Eg. "w, x; y, z" is equivalent to "(w, x); (y, z)".

Subexpressions are computed before their outer expression but independently of each other.
Eg. "a b (c d)":
Compute a, b, c, and d in parallel against the same current thread.
Once c and d return, compute (c d) against the merged thread result of c and d.
Once a, b, and (c d) return, compute (a b (c d)) against the merged thread result of a, b, and (c d).

Meta Interface

Every function has an meta value that provides general object behavior like printString and type.

"Meta x" returns x's meta value (by applying x's class meta function to x).

Currying

If not enough arguments are supplied to a block, a new block is returned that upon being applied to more arguments applies the original block to all the arguments so far.

If too many arguments are supplied to a block, the block is applied to its expected arguments, then the result is applied to the remaining arguments.

Currying is not performed if a block takes variable number of arguments.

Currying supports the illusion of records accepting a full message while they only accept the message selector as input. The record returns a block which is then applied to the message arguments.

No VM, Just Kernel Functions

No separate VM. Everything is in the image, including the machine code.

Everything in the image is a function (tuple, executable, or smallInteger), including machine code, compiler, garbage collector, function applicator, etc.

The Kernel record holds low-level, privileged functions. You can only acess the Kernel if you are given it (capabilities).

Abstract Syntax

Only three types of expressions:
Literal
ThisContext
Application

ThisContext returns the current context (activation record). It provides access to its lexical environment, thread environment, and continuation. And it inherits from its lexical environment for direct access to named values.

A name reference such as "x" translates to:
(Apply ThisContext (Literal 'x)).

A name definition such as "Define 'x 2" translates to:
(Apply (Apply ThisContext (Literal 'Define))
(Literal 'x)
(Literal 2))

LE Concrete Syntax

Start:		Serial | Statement
Serial:		Statement ; Statement | Serial ; Statement
Parallel:	Expression , Expression | Parallel , Expression
Statement:	Parallel | Expression
Expression:	Apply | Primary
Apply:		AdjacentApply | DotApply | SpacedApply
AdjacentApply:	Primary Primary | AdjacentApply Primary
DotApply:	Secondary . Secondary | DotApply . Secondary
SpacedApply:	Tertiary <space> Tertiary | SpacedApply <space> Tertiary
Primary:	SubExpression | Block | Name | Literal
Secondary:	AdjacentApply | Primary
Tertiary:	DotApply | Secondary
SubExpression:	( Start )
Block:		[ Start ]
Name:		<letters>
Literal:	Integer | Symbol | String | Quote
Integer:	<digits>
Symbol:		<symbols>
String:		` <chars> `
Quote:		' Primary

Comment:	" <chars> "

Eg.	L 'Factorial [
			A 'n;
			n<=0
				[1]
				[n * Factorial.n-1]]