File under: Development, throff, golang, lexical, environments

blog/ Lexical Environments in Throff

Throff has extra rules for variables. Here's why, and how.

=== Binding Rules

Variables in most languages are very simple. You type

	x = "hello world"

	print x

And you see "hello world". Easy. Or is it?

	x = "hello world"

	
	function sayHi () {
		x = "goodbye world"
		print x
	}

There is a potential bug here. Did you really mean to overwrite, or "shadow" x? Maybe you did, and the code is correct. But maybe you really wanted to set z to "goodbye world", and accidentally typed x instead.

Throff provides a way to avoid this bug, and in addition, speed up your program by helping the JIT compiler.

	DEFINE  sayHi => [
		PRINTLN x
		REBIND x => "goodbye world"
	]
	
	: x => "hello world"

In Throff, the colon : declares a variable for the first time. If you want to "change" the binding later, you must use REBIND. REBIND only works if x is already bound. If x is not bound, REBIND quits with an error. : does the opposite. It binds a variable only if it was never bound before. If x is already bound, : quits with an error.

	DEFINE  sayHi => [
		PRINTLN x
		: x => "goodbye world"		<- ERROR
	]
	
	: x => "hello world" 
	DEFINE  sayHi => [
		PRINTLN x
		REBIND x => "goodbye world"
	]
	
	REBIND x => "hello world" 		<- ERROR

Note that rebinding screws up referential transparency, and will usually cause the optimiser to fail. Where possible, declare a new variable rather than rebinding.

=== Mutation - not

All data in Throff is immutable. This is required for referential transparency, which is a good thing. However namespaces are not totally immutable, like in functional languages. Instead, you can alter variable bindings with REBIND. The change is then visible in your current scope, and all child scopes. However, it is impossible to alter variables in the parent scope. A quick example demonstrates:

	PRINTLN X
	
	CALL [ REBIND X => CHANGED ]
	
	: X ORIGINAL

will always print

	ORIGINAL

the changes to X are only visible inside the [ ] brackets. Outside, X keeps its original value.

In general, you must not alter variables outside of the scope they are declared in. However there are two legitimate ways to do this

=== Thin functions and Macros

Normally, every time you see [ ] brackets in Throff, they will create a new scope inside. Changes in this scope are not visible outside of the [ ] brackets. But there are two ways to alter bindings outside of the current scope.

==== Macros

Macros are functions that don't have any scope. Instead, they run in the caller's scope. Macros are heavily used in Throff to implement namespace functions, such as the WITH keyword:

	PRINTLN C
	
	WITH [ A B C ] FROM H[ A => 1 B => 2 C => 3 ]H

will print "3".

Macros are defined with the MACRO function

	DEFINE SETX => MACRO [ : X => 10 ]

This will set X to 10 in the caller's namespace, not in the place where the macro is defined.

Macros are good candidates for JIT optimisations, unfortunately the optimiser is not written yet.

==== THIN functions

THIN functions re-use their parent's namespace, instead of creating a new one for themselves. They are most useful in constructs like if statements and loops.

	PRINTLN X
	
	CALL THIN [ REBIND X => CHANGED ]
	
	: X ORIGINAL

will print "CHANGED"