Skip to content

Overview

Chris Harvey edited this page Jul 10, 2024 · 8 revisions

This page is a quick overview of the Solid language. For details, read the Reference and the Specification.

Subsections containing “(FUTURE)” describe planned features that are not yet implemented.

Comments

% line comment (no line breaks)

%% block
comment, with
line breaks,
no nesting %%

Declarations & Assignments

Variables

let variable_name: TypeExpr = expr;

let unfixed reassignable: TypeExpr = expr;
set reassignable = other_expr;

let 'váriåblė nāmė': TypeExpr = expr;

let optional_variable?: TypeExpr;
optional_variable == null; %== true
set optional_variable = expr;

Type Aliases

type TypeName = TypeExpr;
type 'Typė Nāmė' = TypeExpr;

Types

Never

functionThrows.(); % never completes execution; throws an error

let n: never = sync {
	throw Exception.("n is never assigned");
};

Keys (FUTURE)

let a: .key = .key;
let b: .'quoted-key' = .'quoted-key';

Null

let n: null = null;

Boolean

let var b: bool = true;
set b = false;

Integer

let var i: int = 42;
set i = -42;
set i = 42_000;

let bin:  int = \b10_1010;
let quad: int = \q222;
let sex:  int = \x110;
let oct:  int = \o52;
let hex:  int = \x2a;
let htd:  int = \z16; % hexatridecimal (base 36)

Float

let var f: float = 4.2;
set f = 42.;
set f = 0.42;
set f = 42_000.0;
set f = 4.00_000_2;
set f = 6.28318e2;  % 628.318
set f = 6.28318e-2; % 0.0314159

String

String Literals

let string: str = "hello world";

let with_line_feed: str = "line feed is
preserved";

let with_line_continutation: str = "line feed is\
converted to a space";

let with_escapes: str = "
	- hello\sworld (space)
	- hello\tworld (tab)
	- hello\nworld (line feed)
	- hello\rworld (carriage return)
	- \'apostrophes\'
	- \% percent signs \%
	- \\ back-slashes \\
	- I \u{2764} (❤) Unicode
";

let with_instring_comments: str = "
	This is a string! % line comment ignored
	This is %% multiline
		comment ignored %% a string!
	Use \% to escape a percent sign.
";

String Templates

let template: str = """hello world""";

let with_line_feed: str = """line feed is
preserved""";

let no_line_continutation: str = """backslash and line feed are\
both preserved""";

let no_escapes: str = """
	- \s \t \n \r do not escape
	- \' \% \\    do not escape
	- \u{2764}    does not escape
""";

let no_comments: str = """
	% in-string line comments are *not* ignored!
	%% in-string multiline comments
		are *not* ignored! %%
""";

let h: str = "Hello";
let w: str = "world";
let var interpolation: str =
	"""{{ h }}, {{ w }}!"""; % "Hello, world!"
set interpolation =
	"""7 * 3 * 2 is {{ 7 * 3 * 2 }}"""; % "7 * 3 * 2 is 42"
set interpolation =
	"""empty {{}} interpolation"""; % "empty  interpolation"

let var interp_comment: str = """
	This is {{ %% multiline comment ignored %% }} string content!
""";
set interp_comment = """
	This is {{ % line comment ignored
	}} string content!
""";

Object

let var anything: Object = null;
set anything = .FORTY_TWO;
set anything = true;
set anything = 42;
set anything = 4.2e+1;
set anything = "forty-two";

Unknown

let anything: Object = any_object;
let literally_anything: unknown = anything;

Operators

Type Operators

type Nullable      = T?; % T | null
type Exceptionable = T!; % T | Exception % (FUTURE)
type GeneratorOf   = T*; % Generator.<T> % (FUTURE)
type PromiseOf     = T^; % Promise.<T>   % (FUTURE)

type Intersection       = A & B;
type Union              = A | B;

type MutableOf = mut T; % allows mutations, state changes

Unary Operators

let is_falsy: bool = !a; % `true` iff `a` is `null`, `false`, of type `void`, or an exception
let is_empty: bool = ?a; % `true` iff `a` is falsy, a zero, or an empty string/collection

let affirm: int | float = +a; % the value of a numeric value, otherwise no-op
let negate: int | float = -a; % negates a numeric value only

let 'await': unknown = a~~; % (FUTURE)
let next:    unknown = a++; % (FUTURE)

Binary Arithmetic

let addition:       int | float = a + b;
let subtraction:    int | float = a - b;
let multiplication: int | float = a * b;
let division:       int | float = a / b;
let expenontiation: int | float = a ^ b;

3   / 2 == 1;
3.0 / 2 == 1.5;

3.0 / 0; % error

4   ^ -2 == 0;
4.0 ^ -2 == 0.0625;

-3  ^ 2  == 9;
-(3 ^ 2) == -9;

a ^ b ^ c == a ^ (b ^ c);

Binary Comparative

let less_than:     bool = a < b;
let less_or_equal: bool = a <= b;
let not_less:      bool = a !< b;

let greater_than:     bool = a > b;
let greater_or_equal: bool = a >= b;
let not_greater:      bool = a !> b;

let instanceof:    bool = a is b;   % (FUTURE)
let notinstanceof: bool = a isnt b; % (FUTURE)

Binary Equality

let identity:    bool = a === b;
let nonidentity: bool = a !== b;
let equality:    bool = a == b;
let inequality:  bool = a != b;

0   === -0;
0   ==  -0;
0.0 !== -0.0; % -0.0 and 0.0 are not identical
0.0 ==  -0.0;
42  !== 42.0;
42  ==  42.0;
""  === "";   % strings are value objects
""  ==  "";

Binary Logical

let and:  obj  = a && b; % short-circuits
let nand: bool = a !& b;
let or:   obj  = a || b; % short-circuits
let nor:  bool = a !| b;

Binary Pipe (FUTURE)

let pipe:   bool = a |> b; % `b.(a)`, but evaluates `a` before `b`
let 'then': bool = a ~> b;

Conditional Expression

let conditional: obj = if a then b else c; % short-circuits

Augmentation for Variable Reassignment (FUTURE)

let x: int = 42;

set x  += expr; % x = x +  (expr)
set x  -= expr; % x = x -  (expr)
set x  *= expr; % x = x *  (expr)
set x  /= expr; % x = x /  (expr)
set x  ^= expr; % x = x ^  (expr)
set x &&= expr; % x = x && (expr) % short-circuits
set x ||= expr; % x = x || (expr) % short-circuits

Data Structures

Tuples

let tuple: [int, float, str] = [3, 4.0, "five"];
let a: int   = tuple.0; % 3
let b: float = tuple.1; % 4.0
let c: str   = tuple.2; % "five"

tuple.-3 == tuple.0;
tuple.-2 == tuple.1;
tuple.3;  % TypeError
tuple.-4; % TypeError

[3, 4.0, "five"] ==  [3, 4.0, "five"]; % equal by component
[3, 4.0, "five"] === [3, 4.0, "five"]; % also identical by value
tuple ==  tuple;
tuple === tuple;

Records

let record: [
	alpha:   int,
	bravo:   float,
	charlie: str,
] = [
	alpha=   3,
	bravo=   4.0,
	charlie= "five",
];
let a: int   = record.alpha;   % 3
let b: float = record.bravo;   % 4.0
let c: str   = record.charlie; % "five"
record.charles; % TypeError

[a= 3, b= 4.0, c= "five"] ==  [b= 4.0, a= 3, c= "five"]; % equal by component
[a= 3, b= 4.0, c= "five"] === [a= 3, c= "five", b= 4.0]; % also identical by value
record ==  record;
record === record;

let delta: int = 16;
let record1: [delta: int] = [delta= 32];
let record2: [delta: int] = [delta= delta]; % outer `delta`
delta   == 16;
record1 == [delta= 32];
record2 == [delta= 16];

let record: [dupe_key: bool] = [
	dupe_key= true,
	dupe_key= false, % Error
];

let override_count: [count: int] = [count= 2];
override_count.count == 2; % not size

Lists

let list: (int | float | str)[] = List.<int | float | str>([3, 4.0, "five"]);
let a: int | float | str = list.[0]; % 3
let b: int | float | str = list.[1]; % 4.0
let c: int | float | str = list.[2]; % "five"
list.count == 3; % size

List.<int | float | str>([3, 4.0, "five"]) ==  List.<int | float | str>([3, 4.0, "five"]); % equal by component
List.<int | float | str>([3, 4.0, "five"]) !== List.<int | float | str>([3, 4.0, "five"]); % but not referentially identical
list ==  list;
list === list; % a pointer is always referentially identical to itself
list ==  [3, 4.0, "five"]; % lists can be component-wise equal to tuples

Dicts

let dict: [:int | float | str] = Dict.<int | float | str>([
	alpha=   3,
	bravo=   4.0,
	charlie= "five",
]);
let a: int | float | str = dict.[.alpha];   % 3
let b: int | float | str = dict.[.bravo];   % 4.0
let c: int | float | str = dict.[.charlie]; % "five"
dict.count == 3; % size

Dict.<int | float | str>([a= 3, b= 4.0, c= "five"]) ==  Dict.<int | float | str>([a= 3, b= 4.0, c= "five"]); % equal by component
Dict.<int | float | str>([a= 3, b= 4.0, c= "five"]) !== Dict.<int | float | str>([a= 3, b= 4.0, c= "five"]); % but not referentially identical
dict ==  dict;
dict === dict; % a pointer is always referentially identical to itself
dict ==  [charlie= "five", bravo= 4.0, alpha= 3]; % dicts can be component-wise equal to records

Sets

let 'set': (int | float | str){} = Set.<int | float | str>([3, 4.0, "five"]);
'set'.[3];   %== true
'set'.[4.0]; %== true
'set'.[5];   %== false
'set'.count; %== 3

let literal: (int | float | str){} = {"five", 4.0, 3};
'set' ==  literal; % equal by component
'set' !== literal; % but not referentially identical

Maps

let map: {int | str  ->  str | [str] | [i: {str -> str}]} = Map.<int | str, str | [str] | [i: {str -> str}]>([
	[1,     "who"],
	["2nd", ["what"]],
	[1 + 2, [i= {"don’t" -> "know"}]],
]);
map.[1]        == "who";
map.["2nd"]!.0 == "what";
map.[3]!.i     == {"don’t" -> "know"};
map.count; %== 3

let literal: {int | str  ->  str | [str] | [i: {str -> str}]} = {
	1     -> "who",
	1 + 2 -> [i= Map.<str>([["don’t", "know"]])],
	"2nd" -> ["what"],
};
map ==  literal; % equal by component
map !== literal; % but not referentially identical

Punning for Record Properties (FUTURE)

let x: int = 42;

[
	y= 43,
	x$,    % x= x
];

Mutable Types

let list: mut List.<int | float | str> = List.<int | float | str>([3, 4.0, "five"]);
set list.[0] = "three";
list == ["three", 4.0, "five"];

let dict: mut Dict.<int | float | str> = Dict.<int | float | str>([
	alpha=   3,
	bravo=   4.0,
	charlie= "five",
]);
set dict.[.alpha] = "three";
dict == [
	alpha=   "three",
	bravo=   4.0,
	charlie= "five",
];

Optional Entries

let tuple: [int, float, ?:str] = [3, 4.0];
let nullish_string: str | null = tuple.2;

let record [
	alpha?:  int,
	bravo:   float,
	charlie: str,
] = [
	bravo=   4.0,
	charlie= "five",
];
let nullish_int: int | null = record.alpha;

Blocks (FUTURE)

Conditional Statements

if expression then {
	run_if_true;
} else {
	run_otherwise;
};

if expression then {
	run_if_true_then_exit;
} else if expression2 then {
	run_if_true_then_exit;
} else {
	run_otherwise;
};

if true then {
	dangling_else;
};

unless expression then {
	run_if_false;
} else {
	run_otherwise;
};

unless expression then {
	run_if_false_then_exit;
} else unless expression2 then {
	run_if_false_then_exit;
} else {
	run_otherwise;
};

unless false then {
	dangling_else;
};

While Loops

while expression do {
	repeat_as_long_as_true;
	if loop_should_end then {
		break;
	} else if iteration_should_stop_but_continue_looping then {
		continue;
	} else {};
};

do {
	run_once_then_repeat_as_long_as_true;
} while expression;

until expression do {
	repeat_as_long_as_false;
	while inner_condition do {
		if continue_inner_loop_only then {
			continue 0;
		} else if continue_inner_and_outer_loops then {
			continue 1;
		} else {};
	};
};

do {
	run_once_then_repeat_as_long_as_false;
} until expression;

For Loops

for index from start to end by incr do {
	"run while `index` is >= `start` and < `end`,
	increasing `index` by `incr` each iteration,
	and re-evaluating `end` each time.";
};

for index from start to end do {
	"run while `index` is >= `start` and < `end`,
	incrementing `index` by 1 each iteration,
	and re-evaluating `end` each time.";
};

for index in 1..10 do {
	"run while `index` is >= 1 and < 10,
	incrementing `index` by 1 each iteration.";
};

for item: T of iterable do {
	"run for each item in the iterable…
	could be a tuple, list, set, or generator.";
};

Block Expressions

let var counter: int = 0;

let expr: str = sync {
	let message: str = "value";
	set counter += 1;
	message;
};
counter == 1;
expr == "value";

let prom: str = async {
	set counter += 1;
	"done";
};
prom != "done";
counter == 1;
prom~~ == "done";
counter == 2;

Async Blocks & Promises

function my_function(x: int, y: float, z: str): bool {
	return true;
}

let promise: Promise.<bool> = async { my_function.(42, 4.2, "0.42"); };

let promise_shorthand: bool^ = promise; %: Promise.<bool>

promise != true;
let awaited: bool = promise~~;
awaited == true;

let call_and_await: bool = (async { my_function.(42, 4.2, "0.42"); })~~;
call_and_await == true;

many.function~~.calls.().and.()~~.awaits.may~~.be.().chained.()~~;

Generators

type StringGenerator = Generator.<str>; %: Promise.<unknown, str>

let generator: Generator.<str> = async {
	let xx: int = 42 + 2;
	yield """{{ xx }}""";

	let yy: float = 4.2 * 2;
	yield """{{ yy }}""";

	yield """{{ "hello" }}{{ "hello" }}""";

	null;
};

let generator_shorthand: str* = generator; %: Generator.<str>

let xxx: [str?, PromiseStatus] = generator++;
xxx == ["44", .PENDING];

let yyy: [str?, PromiseStatus] = generator++;
yyy == ["8.4", .PENDING];

let zzz: [str?, PromiseStatus] = generator++;
zzz == ["hellohello", .PENDING];

let last: [str?, PromiseStatus] = generator++;
last == [null, .FULFILLED];

many.function++.calls.().and.()++.nexts.may++.be.().chained.()++;

Functions (FUTURE)

function my_function(x: int, y: float, z: str): bool {
	return true;
}
function func_implicit_return(x: int, y: float, z: str): bool
	=> true;

let result:     bool = my_function.(42, 4.2, "0.42");
let call_named: bool = my_function.(42, z= "0.42", y= 4.2);

function return_nothing(): void {
	print.("void function returns nothing");
}
let not_a_value: void    = return_nothing.(); %> Error
let not_a_value: unknown = return_nothing.(); %> Error

function overloaded(x: int): void {
	print.("overload 1");
}
function overloaded(y: int): void {
	print.("overload 2");
}
function overloaded(z: str): void {
	print.("overload 3");
}
overloaded.(0);    %> "overload 1"
overloaded.(x= 1); %> "overload 1"
overloaded.(y= 1); %> "overload 2"
overloaded.("10"); %> "overload 3"

let my_lambda: (int, float, str) => bool = (x, y, z) { return true; };
let lambda_implicit_return: (int, float, str) => bool = (x, y, z) => true;

function higher_order_function(fn: () => bool): void {
	fn; % lambdas can be referenced uncalled
	let b: bool = fn.();
}
let higher_order_lambda: (() => bool) => void = higher_order_function; %> Error: uncalled named function

Named Parameters & Arguments

function named_params(x: int, y: float, z: str): bool { return true; }
named_params.(x= 42, y= 4.2, z= "0.42"); % valid
named_params.(42, 4.2, "0.42");          % also valid

let lambda_named_params: (x: int, y: float, z: str) => bool = (x, y, z) { return true; };
lambda_named_params.(x= 42, y= 4.2, z= "0.42"); % valid
lambda_named_params.(42, 4.2, "0.42");          % also valid

function named_aliases(x= a: int, y= b: float, z= c: str): bool {
	x; y; z; %> ReferenceErrors
	a; b; c; % valid
	return true;
};
named_aliases.(x= 42, y= 4.2, z= "0.42"); % valid
named_aliases.(a= 42, b= 4.2, c= "0.42"); % invalid!

let lambda_named_aliases: (x: int, y: float, z: str) => bool = (x= a, y= b, z= c) {
	x; y; z; %> ReferenceErrors
	a; b; c; % valid
	return true;
};
lambda_named_aliases.(x= 42, y= 4.2, z= "0.42"); % valid
lambda_named_aliases.(a= 42, b= 4.2, c= "0.42"); % invalid!

let lambda_unnamed: (int, float, str) => bool = (x, y, z) { return true; };
lambda_unnamed.(42, 4.2, "0.42");          % valid
lambda_unnamed.(x= 42, y= 4.2, z= "0.42"); % invalid!

Punning for Funcion Calls

let x: int = 42;

f.(
	44,
	y= 43,
	x$,    % x= x
);

Optional Parameters

function myFunction(
	required: int,
	optional1: int = default1,
	optional2: int = default2,
	optional3= opt3: int ?= default3,
): void {
	required;  % int
	optional1; % int
	optional2; % int
	optional3; %> ReferenceError
	opt3;      % int
}
% typeof myFunction == "(required: int, optional1?: int, optional2?: int, optional3?: int) => void"

myFunction.(a);                                % `myFunction.(a, default1, default2, default3)`
myFunction.(a, b);                             % `myFunction.(a, b, default2, default3)`
myFunction.(a, b, optional2= c);               % `myFunction.(a, b, c, default3)`
myFunction.(a, b, optional3= d);               % `myFunction.(a, b, default2, d)`, but evaluates `d` before `default2`
myFunction.(a, b, optional1= c);               % `myFunction.(a, c, default2, default3)`, but evaluates `b` before `c`, then discards `b`
myFunction.(a, b, optional3= d, optional2= c); % `myFunction.(a, b, c, d)`, but evaluates `d` before `c`

Type Functions (FUTURE)

typefunc Box<T> => [value: T];
type S = Box.<int>;
type R = Box.<T= int>;

typefunc Or.<A, B= Bravo ?= null> = A | Bravo;
type U = Or.<int>;              %: int | null
type V = Or.<B= float, A= int>; %: int | float