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!
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.
Compact, generate tiny Wam output and DCE friendly
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?
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.
(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.)
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
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.