Comparing Objective Caml and Standard ML
adam.chlipala.net
adam.chlipala.net
I remember reading this page making up my mind when choosing between the two.
OCaml has, unlike Standard ML, grown quite a lot since this page was made.
In particular, the section "Standard libraries", I'd recommend looking at:
https://dev.realworldocaml.org/
A couple of places where the comparison is outdated:
- OCaml using Base [1] allows for result-type oriented programming
- OCaml using Base uses less language magic and more module system
While there was and is truth to the distinction that SML is for scientists and OCaml is for engineers, this dichotomy is getting dated: OCaml is under active development, which means that scientists who want better tooling will choose OCaml. For example, 1ML [2] by Andreas Rossberg was built in OCaml.
[1]: https://opensource.janestreet.com/base/ [2]: https://github.com/rossberg/1ml
Libraries are underdocumented and unavailable. The build system and package management is arcane. SML feels like a toy language compared to working in OCaml.
But OCaml has had more momentum, and I don't know if anybody is even using SML anymore?
Interesting to look back with a bit of history now, too. Choices like having objects/classes and exceptions (instead of result types) added into OCaml were probably seen as modernizing and adding in features that people expected; but now the pendulum seems to have swung against those features. OOP hype has passed -- e.g. Rust and Go don't have 'classes' with inheritance. And exceptions are also lacking in Rust etc, in favour of Option/Result type + pattern matching, so that control flow is more explicit and easier to reason about.
If exceptions have a "comeback" (they have not gone away in mainstream languages like Java, C#, Python etc.) I hope they come back in a way where they're bundled with static analysis features that help with the reasoning process.
When I worked in Java, 10+ years ago, checked exceptions were considered an obnoxious "no-no", and bad style. Mostly because people just wrapped and rethrew them as runtime exceptions. But, like, runtime exceptions are awful, and almost every application I worked in had buckets of garbage in the logs which consisted of uncaught or "caught & logged" exceptions. Such exceptions are particularly troublesome in highly concurrent applications.
Exceptions should be exceptional. I think Rust has made the right call here. Handle the error, or panic. Don't make it somebody else's problem.
I like that in Java, units of work can continue after an unhandled exception because you know, sometimes, your software is used by many people in many input variants and say, if you re doing a trading backend, it's bad that you cant fill an order because of a silly parsing bug, but it d be way worse if you had to stop for the day until a dev wakes up and fix it.
Log it, and while you fix it the thing still runs for 99% of inputs. Maybe that s what you call "handling" the error ? But it's cool to bubble up the exception because you can share the handler amongst all your downstreams: you may dislike having to do the same exact semi-complex log building everywhere and having it just capture exception at the top most unit of work dispatcher to catch if one threw something to then log and alert your support team in one place, might make sense.
Ofc return types can do all that but you contaminate your whole program with handling for bugs you cant well predict the nature off... the only certain thing is that if your program is old and big enough, you'll screw up in innovative ways a general catch will allow to recover from, because you just dismiss the whole input and move to the next.
If it's an "expected" runtime condition that you can manage and recover from, then it's not "exceptional", is it? So don't use an exception. Pass the information to the caller that needs it, and adjust state accordingly.
That's my take these days. I've seen too many systems degrade in cascading failures because of misguided attempts to "recover." Deadlocks, partial failures, explosions, etc. Real fun to diagnose.
Why are exceptions supposed to be rare ? They re exceptional in the context of what we told the software could happen, but not in the context we're failible humans pissing code as fast as clients can pay us.
I never had problem to diagnose a corrupted state following an exception, it's pretty clear. It s much harder to tell dozens of clients that there will no trading in Hong Kong this afternoon because one of them sent an illegal character we didnt think to sanitize, or the exchange inverted two messages against their spec, or a network router dropped a packet. All these are cases I ve seen the last few years, we lost one order in each case, kept the million others trading as normal, handled the potential surprise the next release...
Recovery design can be done but you need a strict set of constraints. How do you even recover with a restart after bug fix ? Takes hours just to do, the world has moved on, your states you recovered are useless, you ll sort it the next day ?
Maybe imagine a plane software stopping all work because the human pressed two buttons at the same time and the programmer, a human too, forgot this possibility ? Or am I misunderstanding you ? Maybe you work on more one off things like data science when you re the only person interacting with the inputs and outputs ?
Still, the catch syntax was very verbose for handling common conditions. Exceptions were the wrong syntactical tool for the job.
By this logic, it kind of seems like no function should ever return errors at all -- handle the situation or panic, right?
Sure, kicking the ball up the chain out of laziness is, well, laziness. But plenty of times, it's done out of the knowledge (or at least hope) that somebody up the chain has a better idea of how to handle the situation than you do.
Consider some kind of validator/parser class/function, with a public entrypoint and a bunch of private subroutines that can fail when they hit invalid input:
public parse() {
try {
this.parseX();
this.parseY();
this.parseFoo();
...
} catch (e) {
throw new Error(...)
}
}
private parseY() {
this.y = this.input.y.map(a => {
if (bad data) throw new Error("corrupt data");
...
})
}
It's common in such situations to want to have one central point to collect errors in order to produce a single error type result.It is possible to manually thread Results all the way through your logic, but that clutters the code with error handling. Rust's Try operator makes it easier but it's still awkward.
Exceptions allow us to concentrate on the two most important parts: where the exceptions are generated, and where they are handled (in this case, the root of the public interface).
I definitely agree that exceptions are not perfect. They have their own flaws, especially for public interfaces. But as control flow they are useful in many cases.
I'd love that too, i'm very much in love with it.
We’ll see the Zigs, Nims and Rusts of the world mature along with special languages for things like tensor processing units. AI will make it much easier for humans to work using those languages.
For example, English-like syntax is out. Cobol has it because it was invented for its predecessor and Cobol's still around, SQL has it because its designers copied Cobol before we'd learned better, and that's it. We still use words in our syntax but explicit block structure with punctuation is now known to be easier to read.
Speaking of block structure: Line numbers are gone. Unrestricted use of goto (as in, using goto to go from anywhere in the program to anywhere else in the program) is also gone. These things aren't fashion: We've learned better. We're more likely to invent a different kind of structure than to go back to that.
Similarly, languages with absolutely no type system are also out: You can have the types on variables, like Haskell, or values, like Python, but choosing to have neither, like BLISS and BCPL, is no longer an option unless you're actually writing in assembly language.
Other things, like column-oriented formatting, are gone because technology moved on. Even Cobol abandoned that one.
That and the community around it was so enamoured with Deep Intellectual Ponderings and Very Novel Arcanities. That was good, sure, but ... hard.
Ah, yeah, it's been worked on before, just found a paper: https://people.cs.uchicago.edu/~jhr/papers/2020/ifl-smlnj-ll...
https://rescript-lang.org/docs/manual/latest/overview
Although personally I prefer the syntax of F# (but I don't like the .NET focus of it).
If you have dependencies it depends. Writing bindings to all of them can take up a lot of time. Gentype from typescript types is often enough, but not always.
Hope to see this one catch on a lot more. It's an incredible alternative to typescript.
Exactly, typescript is a highly complex addition to a relatively messy language (JS).
Rescript feels like what Typescript could have been, a cleanup of JS and a sound typesystem and excellent type inference. (however breaking compatibility as a consequence)
IMO Rescript is easier to read than plain JS and still it's fully typed.
Anyway thanks idk shit about type theory and I didn't go to college I just write code for money.
The only option is using the include keyword in modules but that's discouraged.
[1] needs updating with OCaml's newest features, but is still very good.
[2] and [3] are useful to see more examples in those respective languages.
[1] https://hyperpolyglot.org/ml
you can highlight your code and run just the highlighted bits, in your REPL.
And for the context, many people from the OCaml side considers that the SML formalization has been for a good part responsible for the freezing of SML since 1997.