Denim

I think Denim needs to be a quite a bit simpler than the original design.

Principles

In order of importance:

  1. Low-mental overhead
  2. Aesthetic
  3. Expeditious

Sub-principles

  • Low-mental overhead
    • Familiar
    • Scannable
    • Aggressive complexity containment
  • Aesthetic
    • Fluent (things ergonomically chain together)
    • DSL friendly
    • Terse
  • Expeditious
    • More meaning in less syntax
    • Highly extensible

Main Ideas

Code Samples

Common-sense builtins

#![allow(unused)]
fn main() {
let u1: u8  = 1u8;
let u2: u32 = 12u32;
let i:  i32 = 123i32;
let i:  i64 = 123i64;
let f1: f32 = 1.2f32;
let f2: f64 = 1.2345e-6f64;

let b: bool = false;
let s: str  = "hello";

let list: [i32]     = [1, 2, 3];
let set:  [:i32]    = [:1, :2, :3];
let map:  [str:i32] = ["a": 1, "b": 2, "c": 3];
}

Dart-style doc comments

#![allow(unused)]
fn main() {
/// This is a doc comment.
///
/// These comments are intended to document your code in greater detail. To
/// facilitate this greater detail, these kinds of comments have:
/// 1. **full** _Markdown_ `support`
/// 2. Dart-style `[]` code links
///    For example, [abc] references the variable created above explicitly.
let forty_two = 42;
}

Go-style packaging and visibility

foo/xy.👖

#![allow(unused)]
fn main() {
type X = i32 | str;
pub type Y = { a: 1, b: false };

fn x() {
  print("x");
}

fn y() {
  print("y");
}
}

foo/z.👖

#![allow(unused)]
fn main() {
pub type Z = X & Y;  // all of foo/xy.👖 is visible to the whole foo/ directory

pub fn z() {
  x();
  y();
}
}

bar/b.👖

#![allow(unused)]
fn main() {
pub type B = Y | Z;

pub fn b() {
  z();
}
---
from ~/foo use Y, Z, z;  // Can only see `pub` decls from `~/foo`
}

Rust-style enums

#![allow(unused)]
fn main() {
// Create an `enum` to classify a notification event. Note how both
// names and type information together specify the variant:
// `MessageReceived != MessageSent` and `UserJoined(String) != UserLeft(String)`.
// Each is different and independent.
enum NotificationEvent {
  // An `enum` variant may either be `unit-like`,
  MessageReceived,
  MessageSent,
  // like tuple structs,
  UserJoined(String),
  UserLeft(String),
  // or c-like structures.
  Reaction { emoji: char, message_id: u64 },
}

// A function which takes a `NotificationEvent` enum as an argument and
// returns nothing.
fn handle_notification(event: NotificationEvent) {
  event.match {
    NotificationEvent::MessageReceived => print("message received"),
    NotificationEvent::MessageSent => print("message sent"),
    // Destructure `username` from inside the `enum` variant.
    NotificationEvent::UserJoined(username) => print("user '{}' joined", username),
    NotificationEvent::UserLeft(username) => print("user '{}' left", username),
    // Destructure `Reaction` into `emoji` and `message_id`.
    NotificationEvent::Reaction { emoji, message_id } => {
      print("reaction '{}' on message {}", emoji, message_id);
    },
  }
}
}

Rust-style pattern matching

#![allow(unused)]
fn main() {
// A function which takes a `NotificationEvent` enum as an argument and
// returns nothing.
fn handle_notification(event: NotificationEvent) {
  event.match {
    NotificationEvent::MessageReceived => print("message received"),
    NotificationEvent::MessageSent => print("message sent"),
    // Destructure `username` from inside the `enum` variant.
    NotificationEvent::UserJoined(username) => print("user '{}' joined", username),
    NotificationEvent::UserLeft(username) => print("user '{}' left", username),
    // Destructure `Reaction` into `emoji` and `message_id`.
    NotificationEvent::Reaction { emoji, message_id } => {
      print("reaction '{}' on message {}", emoji, message_id);
    },
  }
}
}

Strict param labeling

#![allow(unused)]
fn main() {
fn short_story(name: str) -> str {
  "My name is $name"
}

print(short_story("Galois"));

fn story(age: i32, name: str) -> str {
  "${short_story(name)} and I am $age years old"
}

print(story(name: "Galois", age: 20));
print(story(age: 20, name: "Galois"));
print(story(20, "Galois"));  // Compile time error

fn tuple_story((age, name): (i32, str)) -> str {
  story(age, name)
}

print(tuple_story((20, "Galois")));
}

Imports at the bottom

#![allow(unused)]
fn main() {
pub fn my_func(ctx: str) {
  let something: SomeType = some_func(ctx);

  let some_value = something.match {
    SomeVariant => 42,
    _ => 0,
  };

  let cool_thing: CoolThing = some_value.a_func_from_a_trait_impl(ctx);

  cooler.cool_down(cool_thing);
}

---
from cool_lib use CoolThing;
from cooler_lib use * as cooler;

from ~/repo_relative/dir/a_file use SomeType::ATraitImpl;

from relative/sub_dir/some_file use
  SomeType,
  some_func,
  SomeEnum::SomeVariant;
}

Mutation semantics

#![allow(unused)]
fn main() {
let immutable_var = (1, 2, 3);

// immutable_var = (2, 3, 4); // compile time error
// immutable_var.0 = immutable_var.0 + 1; // compile time error

let mut mutable_var = (1, 2, 3);

mutable_var = (2, 3, 4);
// mutable_var.0 = mutable_var.0 + 1; // compile time error

let mutable_val_in_immutable_var = (1, 2, 3).mut;

// mutable_val_in_immutable_var = (2, 3, 4); // compile time error
imutable_val_in_mmutable_var.0 = imutable_val_in_mmutable_var.0 + 1;

mutable_var.0 = 2;
mutable_var.1 = 3;
mutable_var.2 = 4;

mutable_var.inc();

print(mutable_var); // Prints "(3, 4, 5)"

immutable_var.inc(); // Compile time error

impl (i32, i32, i32) {
  fn inc(mut self) {
    self.0 = self.0 + 1
    self.1 = self.1 + 1
    self.2 = self.2 + 1
  }
}
}

* not any

Denim uses the equivalent of TypeScript's unknown, but calls it * instead.

#![allow(unused)]
fn main() {
let some_json = "{hello: \"world\"}";

let parsed_json: {str: *} = parse_json(some_json);

let world = some_json["hello"];

world.lower.print(&mesage); // Compile time error because `world` is `*`.

world.as(str).lower.print(&mesage); // 👌🏾
---
from convert use parse_json;
}

Keyword suffixing

#![allow(unused)]
fn main() {
choice.match { Yes => print("yes!"), No => print("no.") };

result.try;

predicate.if { print("true") } else { print("false") };

eventual.await;

operation.async;

let three_point_o = 12.as(f32) / 4;

let ref_to_some_fn = some_fn.fn;

[1, 2, 3].mut.add(4);
}

Optional chaining

#![allow(unused)]
fn main() {
type Node<T> = {
  next: Node?,
  val: T,
};

fn fifth_node<T>(node: Node<T>) -> T where T: str|i32 {
  node.next.next.next.next.or_else("fallback") // No need for `?.`-style chaining - it is built in
}
}

Selfification

#![allow(unused)]
fn main() {
fn add(a: i32, b: i32) -> i32 {
  a + b
}

print(add(40, 2));
print(40.add(&a, 2));
}

Getterification

Caveats:

  • .fn for function references
  • Must return something
#![allow(unused)]
fn main() {
fn rand(min = 0.0, max = 1.0) -> f32 {
  let (actual_max, actual_min) = if max > min { (max, min) } else { (min, max) };

  (actual_max - actual_min) * secure_rand.trunc + actual_min
}

print(rand(min: -2.0, max: 2.0));
print(rand(max: 23.0))
print(rand());
print(rand);

fn print_hello() {
  print("hello")
}

print_hello; // compile time error
---
from crypto use secure_rand;
}

body param

Inspired by Kotlin trailing lambdas.

#![allow(unused)]
fn main() {
fn do(when: Time = Time::now(), body: fn() -> void) {
  pause(until: when);

  body();
}

do(when: Time::now(), body: || print("hello world"))

do(Time::now()) {
  print("hello world")
}

do {
  print("hello world")
}

fn do_with_args(body: fn(args: *) -> void) {
  body("args")
}

do_with_args {
  print(it);
}

fn do_with_return(body: fn() -> u8) {
  print(body);
}

do_with_return {
  8
}
---
from clock use pause, Time;
}

Operator aliasing for Option and Result

#![allow(unused)]
fn main() {
fn why_write(a: Option<T>) {}
fn when_you_could_write(a: T?) {}

fn why_do(a: Result<T>) {}
fn when_you_could_do(a: T!) {}

fn why_use_many_word(a: Result<Option<T>>) {}
fn when_few_word_do_trick(a: T?!) {}
}

Destructuring

#![allow(unused)]
fn main() {
let tuple = (1, 2, 3);

let (a, b, c) = tuple;
let (..a_and_b, still_c) = tuple;
let (a, b, ..c_in_a_tuple) = tuple;

let struct = { w: (1.1, 2.2), x: 3, y: "4", z: false };
let { w: (w1, w2), x, y, z} = struct;
let {..everything_but_z, z} = struct;

let list = [1, 2, 3];
let [first_from_list] = list;
let [_, second_from_list] = list;

let map = ["i": -1, "j": -2, "k": -3];
let ["i": i, ..everything_but_i] = map;

enum E {
  Bools { m: bool, n: bool },
  Nums(i32),
  Nothing,
}

let e1 = E::Bools { m: false, n: true };
let e2 = E::Nums(0.01);
let E::Bools { m } = e1;
let E::Nums(num) = e2;

}

Autoboxing

#![allow(unused)]
fn main() {
let optional_num: i32? = 123; // `123` gets turned into `Some(123)` automatically
fn box_me(n: i32) -> i32? {
  n
}

let str_result: str! = "hello"; // `"hello"` gets turned into `Ok("hello")` automatically
fn box_me_again(s: str) -> str! {
  s
}

let optional_bool_result: bool?! = true; // `true` gets turned into `Ok(Some(true))` automatically
fn box_me_baby_one_more_time(b: bool) -> bool!? {
  b
}
}

Lexical concurrency

#![allow(unused)]
fn main() {
fn waste_time() -> i32 {
  // t = 0s
  sleep(1.seconds);
  let a = 1;
  sleep(50.seconds);
  let b = 2;
  sleep(2.seconds);

  // t = 50s
  let eventual = sleep(1000.seconds).async;

  // t = 50s
  eventual.await;

  // t = 1050s
  a + b
}
}

Anonymous structs

#![allow(unused)]
fn main() {
type Foo = { foo: bool };
type Bar = { bar: i32 };

type FooBar = Foo | Bar;

fn print_foobar(foobar: FooBar) {
  let { foo, bar } = foobar;

  foo.match {
    Some(foo) => print("foo: $foo"),
    None => print("bar: ${bar.unwrap}}"),
  }
}
}

A little of everything

  • Common-sense builtins
  • Dart-style doc comments
  • Go-style packaging and visibility
  • Rust-style enums
  • Rust-style pattern matching
  • Strict param labeling
  • Imports at the bottom
  • * not any
  • Mutation semantics
  • Keyword suffixing
  • Optional chaining
  • Selfification
  • Getterification
  • body param
  • Operator aliasing for Option and Result
  • Destructuring
  • Lexical concurrency
  • Anonymous structs
  • Minimum viable operators
  • Function overloading
  • Trait-based inheritance
  • impl anything
#![allow(unused)]
fn main() {
enum Weather {
  Cloudy,
  Sunny { uv_index: f32 },
  PartlyCloudy,
  Rainy(RainForecast),
}

/// Fetches information about the wetaher for the specified [date] in the
/// location indicated by [zip].
pub fn fetch_weather_data(date: Date, zip: ZipCode) -> [WeatherData]! {
  let raw_weather_data =
      fetch("https://theweather.com/$zip?date=${date.as_dd_mm_yyyy}")
      .try
      .as_str;

  raw_weather_data
    .lines
    .map(|line| WeatherData::parse(line))
    .collect(to_result_list.fn)
    .ctx("Parsing each line of raw weather data")
}

fn seven_day(zip: ZipCode) -> [WeatherData!] {
  let today = Date::today;

  let list_of_eventuals: [Eventual<WeatherData!>] = 0..7.iter()
      .map(|day_offset| today.prev_day(day_offset))
      .map(|date| date.fetch_weather_data(&date, zip).async)
      .collect(to_list.fn)

  let eventual_of_list: Eventual<[WeatherData!]> = list_of_eventuals.flattened;

  eventual_of_list.await
}

impl DoI {
  fn need_an_umbrella_today(home_zip: ZipCode, work_zip: ZipCode) -> bool {
    // The two invocations of `fetch_weather_data` happen concurrently:
    let home_weather_data = fetch_weather_data(date: Date::today, zip: home_zip);
    let work_weather_data = fetch_weather_data(date: Date::today, zip: work_zip);

    home_weather_data.matches(Weather::Rainy)
      or work_weather_data.matches(Weather::Rainy)
  }
}

impl WeatherData {
  fn as_weather(self) -> Weather {
    self.match {
      WeatherData { rain_data } if rain_data.pct_chance_of_rain > .5 =>
          Weather::Rainy(RainForecast::from(rain_data))
      WeatherData { sun_quotient, uv_index } if sun_quotient > .7 =>
          Weather::Sunny { uv_index },
      WeatherData { sun_quotient } if sun_quotient <= .7 and sun_quotient > .25 =>
          Weather::PartlyCloudy,
      WeatherData { sun_quotient } => Weather::Cloudy,
    }
  }
}

---
from location use ZipCode;
from time use Date;

from ~/my/weather/lib use WeatherData;

from ./util/web use fetch;
}