-
Notifications
You must be signed in to change notification settings - Fork 18
Description
It is fairly straightforward (technically) to add an alternative syntactic "skin" to Morel. This skin would contain the same concepts as the current Standard ML-like syntax but might be easier to learn and write in. With this in mind, we propose the following syntax that is similar to Rust.
The basic idea is to replace keywords as block delimiters - in particular the let .. in .. end construct - with braces. So,
let
val x = 1
val y = 3
in
x + y
end
becomes
{
let x = 1;
let y = 3;
x + y
}
Comment syntax is // and /* .. */, like Rust.
if no longer has a then:
if n < 1
1
else
factorial (n - 1)
Anonymous records are a problem. They an important part of Morel syntax (and Standard ML), and are not present in Rust. Usually we can disambiguate by looking for a semicolon:
// Returns a record
if b
{x, y: z + 5}
else
r
// Executes a block
if b {
let z = y + 5;
z + x
} else {
x
}
If a block contains a single identifier, it is ambiguous:
if b {
x
}
else r
To tie-break, we declare that the above is a block, not a record. If you want a record, use a colon:
if b {
x: x
} else r
For matches, replace the case keyword with match. Morel's
case lst of
[] => "The list is empty."
| x :: nil => "The list has one element: " ^ (Int.toString x)
| x :: y :: rest => "The list starts with two elements: " ^ (Int.toString x) ^ " and " ^ (Int.toString y)
| _ => "The list has other elements."
becomes
match lst {
[] => "The list is empty."
x :: nil => "The list has one element: " ^ (Int.toString x)
x :: y :: rest => "The list starts with two elements: " ^ (Int.toString x) ^ " and " ^ (Int.toString y)
_ => "The list has other elements."
}
In Standard ML, there is a nice parallel between lambda syntax fn match and match syntax case exp of match. So, lambda syntax evolves to
fn {
[] => "The list is empty."
x :: nil => "The list has one element: " ^ (Int.toString x)
x :: y :: rest => "The list starts with two elements: " ^ (Int.toString x) ^ " and " ^ (Int.toString y)
_ => "The list has other elements."
}
As in Rust, braces are optional if a match has a single arm. So
fn n => n + 1
remains valid.
Maybe add "if let", based on Rust's "if let", e.g.
let y =
if let SOME n = z
n
else
0;
is shorthand for
let y =
match z {
SOME n => n
_ => 0
};
Function declarations should use let (replacing fun):
let plus x y = x + y
Function application remains like Standard ML. a b applies function a to argument b. No parentheses are required.
For declaring sum types we switch from datatype to enum. Thus:
datatype 'a tree = Empty | Node of 'a * ('a tree) * ('a tree)
becomes
enum 'a tree {
Empty,
Node of 'a * ('a tree) * ('a tree)
}
For type notation, I don't know whether we should switch from prefix (e.g. int tree) to postfix (e.g. tree<int>).
I think type variables (e.g. 'a) need to remain syntactically distinct. In Standard ML you don't need to introduce type variables when declaring a generic function, and we want to continue that. For example, e.g.
fun len (x: 'c list) = if null x then 0 else len (tl x);
val len = fn : 'a list -> int
For boolean expressions, andalso and orelse become && and ||.
Trailing commas are now allowed, e.g.
// A list
[
1,
2,
]
// A record
{
x: 1,
y: "hello",
}