Skip to content

Rust-like syntax #307

@julianhyde

Description

@julianhyde

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",
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions