Currently, string and char literal are just syntactic sugar for array of bytes and bytes encoded in utf8. Unicode support shall be library features:
- Uses UTF-8 encoding.
- There shall be a string type that simply wraps array of numbers.
- Validity of such type shall be enforced by refinement types. Unsure for this, might be a challenge or simply impossible.
- Another type for Unicode bytes and scalar values.
Doesn't evaluates to string but maybe to something like Iter(Str) for efficiency.
print_line(f"value is \(value)");
Debug printing, pretty printing, padding, alignment, etc. shall use normal functions instead of special syntaxes.
Debug printing and pretty printing
print_line(f"value is \(debug(value))");
print_line(f"value is \(pretty_print(value))");
Padding, alignment etc.
name = pad_end(name, 20);
value = pad_start(value, 5);
print_line(f"\(name) \(value)");
Multiline strings are automatically dedented.
map_option(match val, mapper) => {
@val val => @val mapper(val),
@none => @none,
}
An alternative to control flow label. This will break or continue the nth closest loop. providing 0 is just the same as not using upto at all.
while @true {
for i in arr {
if i == 2 {
break upto 1;
}
}
}
You may use keyword instead. It won't work when there's multiple loop with the same keyword.
while @true {
for i in arr {
if i == 2 {
break upto while;
}
}
}
Instead of upto, at keyword may be used.
Will be provided by std library.
alias Option(a) = @val a | @none;
Iterators will use existential types.
:(a):
trait Iterator(a) {
alias Item;
next(self : &:mut a) -> Item;
}
alias Iter(a) = impl(b) b where Iterator(b).Item = a;
#["apple", "banana", "cherry"]
#[1 .< 3]
Iterator comprehension
#[val; for val in array]
-- if guards
#[val; for val in array; if val > 0]
-- skip when pattern matching fails
#[val; for @val val in array]
mod math {
share pi = 3.14;
sqrt(num) => {
-- ...
}
}
Module in different file.
mod math;
Importing from nested module, would be similar to declaration.
pi = math.pi;
-- or with unpacking pattern
(= pi) = math;
-- or with left to right declaration
math =: (= pi);
Special names
selfsuperlibglobal
Only usable for modules, not records.
(*) = math;
With declaration shorthand.
= math.*;
pub greet(name) => "hello " ++ name ++ "!";
-- public to only select module
pub(path.to.module) greet(name) => "hello " ++ name ++ "!";
"hello world" |> print_line;
add(a, b) => a + b;
result = 40 |> add(?, 2);
Provided by std library.
share foo = cell(10);
share bar = foo;
mut num = extract_cell(&foo);
num^ <- num^ + 1;
assert(extract_cell(&foo) == 11);
It should never be reachable enforced by refinement type. Provided by std library.
-- this could be in std
expect(condition) => if condition {} else { never() };
prime_factor(num) => {
expect(num % 1 == 0);
expect(num >= 1);
if num == 1 {
[]
} else {
for i in #[2 .. num] {
if num % i == 0 {
return [i] ++ prime_factor(num / i);
}
}
never()
}
}
map_tagged(val, tag, fn) => match val {
@$tag val => @$tag fn(val),
val => val,
}
map_tagged(val, "val", (val) => val + 3);
:(a):
trait Eq(a) {
equal(left : &a, right : &b) -> Bool;
}
:(a):
where Eq(a):
impl Eq([a]) {
equal(left, right) => {
if left^.len /= right^.len { return @false; }
for i in #[0 .< left^.len] {
if left^[i] /= right^[i] { return @false; }
}
@true
}
}
Implementing traits on tuple
impl Eq(()) {
equal(left, right) => true;
}
:(a, rest):
where Eq(a):
where Eq(rest):
impl Eq((a, *rest)) {
equal(left, right) => {
&>(left, *left_rest) = left;
&>(right, *right_rest) = right;
left == right && left_rest == right_rest;
}
}
Implementing traits on records with meta-programming using compile-time variable holding identifier
TODO: ordering of fields, HOW?
impl Eq(()) {
equal(left, right) => true;
}
:(name, a, rest):
where Eq(a):
where Eq(rest):
impl Eq(($name : a, *rest)) {
equal(left, right) => {
&>($name = left, *left_rest) = left;
&>($name = right, *right_rest) = right;
left == right_rest && left_rest == right_rest;
}
}
On tagged union, again with meta-programming.
impl Eq(Never) {
equal(left, right) => never();
}
:(tag, a, rest):
where Eq(a):
where Eq(rest):
impl Eq(@$tag a | rest) {
equal(left, right) => {
match (left, right) {
(&>@$tag left, &>@$tag right) => left == right;
(&>@$tag _, _) | (_, &>@$tag _) => @false;
(left, right) => left == right;
}
}
}
value : impl Eq;
More complex syntax:
value : impl(a) a where Eq(a);
-- declaration
pub newtype Point(
x : Num,
y : Num,
);
-- creation
point = Point(x = 10, y = 20);
It won't have trait implementation by default and have it's own refined types.
Generics:
:(a):
pub newtype Extended(@neg_inf | @fin a | @inf);
-- or
pub newtype Extended:(a)(@neg_inf | @fin a | @inf);
To achieve nominal typing, instances are simply regular record or tuple but it holds a value on the special field __nominal__. The type of this value is also a special type acting like a unit but has nominal typing. This special type is accessible from Type.NominalUnit where Type is the name of the type.
point = Point(x = 10, y = 20);
-- is similar to
point = (__nominal__ = Point.NominalUnit(), x = 10, y = 20);
The special field and type should only be relevant when implementing auto trait implementation.
__nominal__ is not a typical field. Tuples can have it. __nominal__ is a keyword and is different from n"__nominal__".
Usually, this will rely on trait implementation on regular tuples and records, making use of the special nominal field if necessary.
auto Eq(a) {
impl Eq(a.NominalUnit) {
equal(left, right) => true;
}
}
impl auto Eq(Point);
-- declaration
newtype Point(
x : Num,
#y : Num,
pub(path.to.module) #z : Num,
);
-- creation
point = Point(x = 10, #y = 20, #z = 30);
-- access
y = point.#y;
-- pattern matching will require explicit record type name
Point(= #y) = point;
-- this is an error
(= #y) = point;
Anonymous record types have all fields public. Private fields are only applicable for newtype. Private fields can have visibility overridden by using pub.
val = a `op` b;