Plange


Plange is an open-source project to create a development environment including a programming language, standard library, and runtime.

Introduction

Plange is an algebra of constraints on data, functions, variables, types, and other objects. It is traditional to start with the Hello World program.

print("Hello, world!");
			

Comments are created using two syntactic variations.

End of line comment (red is comment text)

print("My name is HAL 9000."); //only kidding!
			

Inline comment (red is comment text) source

getRandomNumber := { return 4; /*choosen by fair dice roll*/ }; //guaranteed to be random
			

Special Characters

Several Unicode characters are included for completeness, but cannot be typed using a keyboard. These characters have typable equivalents for convenience. For example may be typed =>. Unicode characters will be used in documentation. A partial list can be find on the Operators page.

Variables

A variable is a symbol that may take place in a constraint system, be free or bound, and may have memory allocated to it. Constraints on variables define a problem space for which solutions are desired. A variable can also contain data and be changed at will by the programmer.

Assign a value to a variable

x ← 1337;
			

Variables can be reassigned.

Reassign a variable

color ← "Blue";
color ← "Red";
			

A variable may be constrained to a type.

Type constrain a variable

<Number> x ← 1337;
			

Unbound variables can be targets for symbolic or numerical solutions.

Symbolic manipulation

x = 1337;
tan(y*2) = x;
print y; // arctan(1337) / 2 = { 1.570048, -1.571544 };
			

Constants

A symbol with a value that is immutable is a constant.

Example

print(π); //print pi
			

The symbol π is an identifier for the pi constant. It can be substituted in places where pi is needed, and provides arbitrarily high precision. It may be typed pi.

Constants are created using the definition operator :=

Example

daysInAWeek := 7;
			

Functions

Create function implementations using parenthesis ( ) containing the parameter list, followed by curly braces { } containing the implementation.

Example

doubler := (x) { return x * 2; };
			

The parameters consist of the single constant x, and the implementation multiplies it by two and returns the value. Functions can have more than one parameter.

Example

geometric_mean := (x, y) { return √(x * y); };
			

Types

Constants and variables can be constrained to a specific type.

Example

<Int> x ← 10;
x ← 1.5; // error - can't assign a fractional Number to an integer
			

"Int" is shorthand for integer. The first line constrains x to be an Int, which means it cannot be assigned a number with a decimal point. The second line demonstrates this restriction. See Type System.

Functions Types

Create function types using the operator, or ->.

Example

<Int → Int> doubler = (x) { return x * 2; };
			

The above code constraints doubler to the function type Int → Int. Simply, doubler is a function takes an Int as an input, and returns an Int as a result. Function arguments can also be type constrained.

Example (continued)

<Int → Int> doubler := (<Int> x) { return x * 2; };
			

Variables that have no specified type constraint are dynamically typed.

Assigning objects of varied type to a variable

x ← 10;
x ← "Alice";
x ← { print("fubar"); };
			

Making Types

The type (not capitalized) keyword is used to make a new Type (capitalized) object.

Example

Color := type {
	<Double> r;
	<Double> g;
	<Double> b;
};

<Color> red ← (| 1, 0, 0 |);

print(type_of( (| 1, 0, 0 |) ));  // output: Tuple<Number, Number, Number>
print(type_of(red));          // output: Color
print(type_of(Color));        // output: Type
print(type_of(Type));         // output: Type
			

Parametric Types

Parametric types are functions that return Type objects.

Example

Node := (<Type> valueType) {
	return type {
		<valueType> v;
		Maybe<Node<valueType>> next;
	};
};
			

Constant folding evaluates most invocations of type functions at compile time. Functions that return Type objects (or another type function) can be called with the angle bracket syntax:

Example

<List<Int>> myList;
			

Pattern Matching

A recursive function to print the last element of a list:

Example

<List<_> → Void> printLast := 
	(_ & tail) { printLast(tail); } |
	(x) { print(x); };

myList := [ 5, 12, 8, 9 ];
printLast(myList);
			

Output

9
			

The prepend operator & takes a value on the left, a list on the right, and produces a new list with the left prepended to the right. In the example above, the first parameter to the function is being broken apart into two pieces.

Note the use of the underscore _ character. It's substituted for a variable when the code doesn't care about the value. In the first line of the example above, we are unconcerned with the type of the elements the input list contains, and only need to ensure that the input is a list of something. In the second line, we don't need to know the value of the head element. The underscore keyword is called dont_care.

Polymorphism

The inheriting keyword, used in conjunction with the type keyword, makes a new Type object inheriting the members of the specified base Types. See also: implementing

Example

// base Type
Widget := type {
	<Void → Image> Paint;
};

// derived Type
TextBox := type inheriting Widget {
	<String> text ← "Hello, world!";

	// override the inherited Paint method
	Paint ← {
		return Drawing.Render(text);
	};
};
			

Algebraic Types

Types can be combined together to make algebraic Types using the compound operator |.

Example

Some := (t) { return type { <t> value; }; };
None := type {};
Maybe := (t) { return Some<t> | None };

<Void → Maybe<Int>> get_age := {
	return coerce(input("What's your age? You don't have to tell me."));
};

print(get_age());
			

Constraint solving

Many interesting problems may be constructed as one or more constraints, using operators (arithmetic, set theoric, quantifiers, etc), and invocations such as calling the sin() function.

Example

children := {| abe, dan, mary, sue |};
ages := {| 3, 5, 6, 9 |};
children ↔ ages; // One child per one age (bijection operator)

abe > dan; //abe is older than dan
sue < mary; //sue is younger than mary
sue = dan + 3; //sue's age is dan's age plus 3 years
mary > abe; //mary is older than abe
			

This code is semantically equivalent to the following:

Example (continued)

abe = 5;
dan = 3;
mary = 9;
sue = 6;
			

One well studied domain is initial value problems.

Example

advanceProjectilePosition := (
		<Vector3D> initialPos,
		<Vector3D> initialVel,
		<Real> mass,
		<Real> drag,
		<Vector3D> gravity,
		<Real> delta_t
) {
	// declare the position function, x
	<Real → Vector3> x;
	
	// model x as a differential equation
	mass * Δ^2x(t)/Δt^2 = -drag * Δx(t)/Δt + mass * gravity;
	
	// with boundary conditions
	x(0) = initialPos;
	Δx(0)/Δt = initialVel;
	
	// solve, substitute, evaluate
	return x(delta_t);
};
			

A closed form solution for x is determined symbolically, such that the following program is functionally equivalent.

Example (continued)

advanceProjectilePosition := (
		<Vector3D> initialPos,
		<Vector3D> initialVel,
		<Real> mass,
		<Real> drag,
		<Vector3D> gravity,
		<Real> delta_t
) {
	//closed form solution computed at compile time
	a := 𝑒^(drag*t/mass);
	return (
		gravity * (mass-(mass*a + drag*delta_t)) + 
		initialPos*a*drag^2 + 
		drag*mass*initialVel*(a-1)
	) / (a*drag^2);
};
			

Example

<Number * Number * Number → Number> linear_interpolation :=
	(min, max, x) { min * (1 - x) + max * x }
;
			
inverted_linear_interpolation := (min, max, interpolated) { 
	interpolated = linear_interpolation(min, max, x); //x is a free variable
	return x;
}

assert(x = inverted_linear_interpolation(y, z, linear_interpolation(y, z, x));
			

Limitations

Constraint solving is intractible in the general case. Users must familiarize themselves with the capabilities of the language, which are expected to expand. A demonstration of a semantically correct but nonfunctional program is in order.

Counter Example

<Collection * BinaryRelation → Collection> sort := (items, ordering) {
	result ↔ items; // result and items make a bijection
	∀ { ordering(result[i - 1], result[i]) | i ∈ (0...|result|) }; //the result has to be sorted
	return result; // solve, substitute, and return
};
			

The above function, sort, is functionaly equivalent to the sorting functions. However, this constraint based problem is not yet solvable using available techniques.

Type Constraints

Since types are values, and values can be constrained, type constraints are realised.

Example

all := (Collection<X> items) {
	Bool casts typeof(X); //values of type X must be castable to type Bool
	
	for (item ∈ items) {
		if (¬(Bool)item) {
			return false;
		};
	};
	return true;
};
			

Further reading


copyright © Brent Lewis 2017