What's your laundry-list of features for an ideal ML?

Speaking with friends online, and later inspired by this topic, I wanted to see what the OCaml community’s idea of a perfect ML dialect looks like…
So here goes, please share the features you wish to see the most. Let’s find out what the community values in a language.

Note: the features can be in the language itself, tooling, ecosystem, or even development and community management approaches. All wishes count!

3 Likes

As with this kind of topics, I’ll start with my own list : P
It’s split on tiers from really-want to nice-to-have

tier 1:

  • ergonomic repl which aids exploration:
    • loads files, opens editor on them, reloads on close
    • pulls docs of any name (even keywords etc)
    • lists definitions in current (or any) namespace
    • offers smart completion
    • shows type signatures, etc…
  • time & memory profiler, gc profiler, all with good ui (preferably gui)
  • powerful debugger with the ability to change code while it’s executing
  • user-level ad-hoc polymorphism
  • user-level infix names (names do include symbols, naturally)
  • a way to partially apply functions (a given, but some languages heavily inspired by the ML family had let that feature go)
  • a good stdlib that covers most programming needs, possibly offloaded to community
  • a good API-search tool (implying a central docs repository, but not necessarily implying a central package repository)
  • tests are first-class in the language
    • you can pass tests around, parametrize them, run them conditionally, etc…

tier 2:

  • the language is bootstrappable in some capacity from some other widely-used language (probably C)
    • just enough to self-bootstrap is fine
  • the compiler(s) is (or are) OCaml-fast
  • the language has both a comprehensive spec and a softer tutorial-style manual with lots of examples
  • the language does not leave matching features on the table (as-, if-, or-patterns et cetera are all supported)
  • the only laziness syntax is at the type level, wrapping and unwrapping lazy values is transparent
  • undecidable type system features enabled for opt-in type signatures… otherwise full inference
  • pluggable lean language runtime or at least pluggable gcs with different allocation strategies
  • variants and records are structurally typed by default, records are row-polymorphic, constructors are functions, fields are projections and injections, etc…
  • all functions are either labelled and positional depending on call site
  • good FFI (honestly I have no idea how that would look like because all FFIs I tried were extremely painful… maybe it’s just something inherent to FFI)
  • good macros / comptime execution / multistage compilation / … something that does program in program
  • generic container syntax a la pyret and overloaded literals
  • selective imports (hopefully with good DCE)

tier 3:

  • unicode identifiers, no case restrictions on types vs modules vs constructors vs values, escapable keywords
  • good multiline strings
  • declarations are order-independent
  • postfix declarations (where) next to usual prefix declarations (let)
  • shadowing is allowed and not penalized in any way
  • the type system can track side-effects and linear resources somehow
    • the language is thus pure, but should preserve the simplicity and flexibility of a regular OCaml semicolon
  • syntax that favours aesthetics over regularity
  • error messages that are allowed to be ad-hoc and per-case rather than waiting for a general principled approach to error reporting
  • the language offers prover-style typed-holes with suggestions from context that only match the type, or some way to search by types within the repl.
  • the repl has hot reloading capabilities
  • language has the ability to talk about memory layout of things, unsafe/unboxed primitives, and support for vectorized ops
    • maybe instead it has a low level subset that should be written in a different file, or marked clearly in sections
    • and maybe also a high level superset that has full blown dependent types or some expressive way to do comptime assertions, that also should be written in a different file or marked clearly.
    • compiler is allowed to be slow when there’s comptime execution of course
  • module system does not care about file or directory structure, filenames, how many modules are in a file, or how many files are in a module
    , the language offers TODO placeholders that it can treat specially
    • for example, the compiler can print a summary of all the TODOs in a codebase
    • a value declared TODO can be used as if it exists, until the runtime needs to evaluate it of course.
8 Likes

A strict version of Haskell. :slightly_smiling_face:

5 Likes

How’s Purescript or Idris? or Haskell’s -XStrict :stuck_out_tongue:

1 Like

As Simon Peyton-Jones is fond of saying: “The next Haskell will be strict. The next ML will be pure.”

2 Likes

Haskell’s type system + conciseness.
OCaml’s module system + object system.
Rust’s low level memory control (including lack of GC) + borrow checker + concurrency.
Lisp’s macro system + REPL.
IntelliJ’s IDE support.
x86_64 assembler’s compile time.

2 Likes

I have been working on something like this for almost a year, we will make a formal introduction next week.

Some highlights:

  1. Fast – check fast (even faster than ReScript) and run fast, whole program optimizer
  2. IDE experience – full IDE even in the browser (try.moonbitlang.com)
  3. Compact, generate tiny Wam output and DCE friendly
  4. Limited ad-hoc polymorphism, break, continue, var and good stuff inherited from ML

@hyphenrf in your checklist, “the only laziness syntax is at the type level, wrapping and unwrapping lazy values is transparent”, do you have any rationale to justify it?

10 Likes

moonbitlang looks really cool – are there any plans to open source the language (and when?) ; I’m hesitant to depend on closed source software

Thanks for your interest. Moonbit has been changing really fast, we plan to make it publicly available when it reaches beta stage.
You can try it now without any installation (using our online IDE), or CLI tools.

3 Likes
  1. Haskell syntax
  2. Haskell-like polymorphic arithmetic operators
  3. Ocaml-style side effects (no need for an IO monad)
  4. Idris/Agda style editor support–generating cases, typed holes, suggesting code.

(About #2: I understand and respect the reasons that some folks prefer OCaml’s approach. Readability of arithmetic is more important, for me, and all of those dots seem noisy. I know that modular implicits might allow a more Haskell-ey syntax. I know that there are ways to get rid of the dots locally within definitions, but I don’t feel that this is a good solution.)

(About #4: Already partly exists in OCaml? I have not fully explored editor integration. But if so, it would be good if such gools were integrated into textbook and tutorial presentations, as it is with Idris and Agda.)

1 Like

In addition to this (minus the Haskell syntax, I don’t have a preference),

  1. Easy tooling and std lib for the web and linux based work with formatter, linter, test runner, build, pkg manager all wrapped up. e.g. Rust, Go, Deno.
  2. Some supervisor-actor concurrency model, e.g. BEAM languages, but this can be done by libraries for the most part.
1 Like

moonbit looks interesting, this is my first exposure to it but it looks fairly new anyway.
ML heritage and WASM as a first-class target brings the bit more established grain to mind. I can see the two being inevitably compared. Most moon- or space rock-related names also happen to come from the Lua side of programming world, that thought did cross my mind I have to admit haha

For the laziness bit, the idea was to try and have more flexible evaluation model choices by making the strict language opt-into laziness via types, relegating the insertion of lazy and force to the compiler allows for more ergonomic code that sort-of “abstracts over laziness”:

// hopefully I didn't mess up the syntax
(&&) : (Bool, Lazy[Bool]) -> Bool
a && b // translates to a && lazy(b)

I’ve taken somewhat less extreme position than the time I posted this, I think you should be able to insert lazy and force at value level optionally if you want to be explicit about it. But I can’t think of counterexamples where this is not an improvement over the status quo. I don’t find neither Haskell’s nor OCaml’s approach more ergonomic than this, and I’ve seen it firsthand with Idris and immediately liked it.

More elaborate example that’s just for demonstration (you can write this in a better way):

struct Person {
  lastName: Lazy[String]
  age: Lazy[Int]
}

let test = {
  let dad = { lastName: "Lazee",  age: sam.age + 32 }
  and sam = { lastName: dad.lastName, age: 24 }
  (dad, sam)
}

/* should translate to:
 * let dad = lazy { lastName: lazy("Lazee"), age: lazy(force(force(sam).age) + 32) }
 * and sam = lazy { lastName: lazy(force(force(dad).lastName)), age: lazy(24) }
 * (force(dad), force(sam))
 *. 

Gonna keep an eye for moonbit, for when the beta is here

I want less features than Ocaml :sweat_smile:
I would like a package manager and a build system integrated in the interpreter. Or at least provided as librairies. So you define the build in the language.

1 Like