Oliver's Programming Language is based on scopes. Value scopes (essentially lambdas) and type scopes (structs). Examples so far found below:
// Fundamental types
u8, u16, u32, u64,
i8, i16, i32, i64,
f32, f64, bool
// Names can be either a value (fundamental type), or a value scope (lambda)
a: f32 = 5; // value
b := () {}; // empty lambda, type of "scope"
// b: scope = () {};
// Anonymous lambda
() {};
// Calling anonymous lambda
() {} ();
// Naming anonymous lambda
my_name := () {};
// Calling named lambda
my_name(); // equivalent to () {} ();
// Called lambdas can have their local names accessed
num: f32 = () { x: f32 = 5; } ().x;
// Named lambdas therefore support this
length := (x: f32, y: f32) {
result := std.sqrt(x * x, y * y).result;
};
len := length(2, 5).result;
// Called lambdas can be cached (with ":>") so they're only evaluated once
add := (a: u8, b: u8) {
result := a + b;
};
add_alias :> add(1, 1); // "add" is evaluated here even though no locals are accessed
num := add_alias.result; // add_alias (and therefore add(1, 1)) is not re-evaluated
num2 := add_alias.result * 2; // again, the "result" value is got from a cache not re-evaluated
// Parameterless lambdas can be cached immediately to create namespaces or enum-like values
my_enum :> () {
first := 0;
second := 1;
third := 2;
} ();
val := my_enum.first;
// So far only value scopes (lambdas) have been discussed
// Type scopes are used instead of fundamental types or the "value_scope" type
Point: {
x: f32 = 0;
y: f32 = 0;
};
p: Point = {}; // Equivalent to p: {x: f32 = 0; y: f32 = 0;} = {};
// = {} is required otherwise p is an alias of the "Point" type scope instead of a value scope name
// the empty value scope makes p use Point's default values. Note that the value scope is NOT a lambda.
// It serves as the structural equivalent to a literal value (1, "", false, etc.)
// It is worth noting that OPL is duck-typed. Two Point structures that are defined in different places
// will compare for equality successfully
// Type scopes act like an array of different types
// Their local names are available in their assigned value scope
p: Point = {
x = 55; // x and y are available from the Point type scope
y = x * 2;
};
// Because names require assignment or else they become type aliases, value names in type scopes
// can be left un-assigned in their value scopes because they have an initial value already
p: Point = {
x = y * 2; // y was assigned 0 in the Point type scope already
};
// Note that type scopes cannot have parameters
Point: () {}; // error!
// It's possible they could in the future for compile-time things, like Zig's functions
Point: (T: type) { // no idea what the "type" would be here
x: T = 0;
y: T = 0;
};
p: Point(f32) = {};
// Type scopes cannot have their local names accessed like value scopes allow
Point.x; // error! Type scopes must be instantiated first
p.x = 5; // OK, value comes from a variable instance
// Only fields which come from a type scope are mutable outside a value scope
maths :> () {
pi := 3.1415;
} ();
maths.pi = 5; // error! pi is const!
// Type scopes can have other type scopes defined within
Box: {
pos: Point = {}; // = {} is required otherwise "pos" is a type scope instead of a value scope name
size: {
w: f32 = 0;
h: f32 = 0;
} = {};
// This is not cached as the inner values can change between calls, so acts more like a normal function
bottom_right := () {
result: Point = {
x = pos.x + size.w;
y = pos.y + size.h;
};
};
};
b: Box = {
size.w = 24;
size.h = 24;
// could do value scope initialisation - size = {w = 24; h = 24;};
};
br := b.bottom_right().result; // br == Point {x = 24; y = 24;};
// Taking a lambda as a function input
func: scope = (pred: scope) {
if (pred().result) {} // etc.
};
// There is no signature for the scope parameter, which leads to unknown signatures if not documented
// Not sure how this would be solved, or if it can. Named scopes can equal anything, a signature is not part of the type!
f: scope = (x: f32) {result := x * 2;};
f(5); // == 10
f = {};
f(); // nothing
/**
* I don't think this is much of a problem though. Scopes already have pretty hidden names that the user
* is expected to know (like the typical "result"), so this isn't hugely different. Just a quirk of the language.
*/