11 ms·
OCaml: a Rust developer's first impressions
- forgotpwd16 3y agoInterestingly the original Rust compiler was written in OCaml. Or to be more precise the one used for bootstrapping rustc.
- sgt 3y agoSeeing his picture, I figured this guy almost looks like a thug, and sure enough he's in prison, albeit not a thug anymore. His story: https://pthorpe92.github.io/intro/my-story/ https://pthorpe92.github.io/intro/my-story/ Looks like he's got everything lined up for success when he one days is let out of prison. Great attitude and lots of experience.
- ReleaseCandidat 3y agohttps://news.ycombinator.com/item?id=38229231 https://news.ycombinator.com/item?id=38229231
- satvikpendem 3y agoOCaml is great, we learned it in class back in college, then we learned Rust right afterwards. Oftentimes people say OCaml is Rust without the borrow checker (or that Rust is OCaml with a borrow checker), but that's not quite true. Since it is entirely functional and recursive, it takes more time to wrap your head around. Now with OCaml 5, we also have algebraic effect support, something that Rust is looking to add in as well, in a more moderate capacity. Also, if you don't like the OCaml syntax, check out ReasonML, which is an alternative OCaml syntax that Facebook developed several years ago. It has the Algol-style that many modern languages today use.
- frodowtf 3y agoPeople say that OCaml is like Rust, but unlike Rust, OCaml has Exceptions that could appear everywhere. How is that safe?
- bananapub 3y agowhy do feel exceptions make a language unsafe?
- timschmidt 3y agoSecond, less-predictable execution path. Additional cognitive load in evaluating effects across both paths. Additional opportunity for bugs. Doesn't necessarily mean that exceptions make all software which uses them unsafe, but does tend to mean that exceptions significantly complicate the task of proving safety for any nontrivial program.
- trealira 3y ago> proving safety for any nontrivial program. I haven't done this, but probably the surest way to do that would be to use a proof assistant and extract the result to Standard ML or to OCaml. Standard ML has verified implementations, too.
- hurril 3y agoSafe isn't the best word to describe it with. But it does mean that any expression or statement always has two possible control flows. You have the "surface flow" as well as the exceptional flow, so there's an added complexity. I never felt this was a problem when I did Java though (despite their awkwardness - basically forcing coders to not use checked exceptions.) Rust's control flow syntax for Results and Options are very similar to this but with an added benefit: you don't have to use the ?-operator. panics is different, however. They are more akin to the way any Java program will happily OutOfMemoryError or NoClassDefFoundError given circumstances not (always) in your control.
- fweimer 3y agoI don't think panics are comparable to Java's VM errors. A lot of libraries (not just the standard library) seem to target panic-safety, avoiding unsafe behavior and resource leaks in case of panics. This means that the panic itself is not supposed to transition the process into an undefined state, like it happens with many VM errors in Java (where a stack overflow may mean that required cleanup action has not executed, for example). With Rust, the overall situation is a bit strange: as a library author, you are expected to deal with the possibility of panics (which gives you all the headaches associated with dealing with exception safety), but as a user, you are not supposed to rely on them. (I expect that most request handler loops will have catch_unwind handlers, to avoid a faulty request taking down the entire process.)
- debo_ 3y agoI think about it the other way; Rust is an ML with a borrow checker. Most of the stuff I hear people gushing about in Rust is IMO them experiencing what's it's like to write ML.
- satvikpendem 3y agoYes and no, Rust is ML inspired but it is still way too imperative and not as functional to be called a true ML. But yes, things like Result and Option types, do-notion via "?," at least for Results and Options, algebraic data types are all part of the appeal. There sadly are still no higher kinded types though, but that is a difficult problem to solve and most won't encounter such problems anyway in day-to-day coding, so the contributors made generic associated types instead as an alternative.
- greydius 3y ago> still way too imperative and not as functional to be called a true ML As a Haskell fanboy, this is how I feel about Ocaml and F#.
- satvikpendem 3y agoWhat are your thoughts on Idris and other Haskell derivatives? As well as the HVM and its Lisp-like Haskell-like combo of a language [0]? [0] https://github.com/HigherOrderCO/hvm https://github.com/HigherOrderCO/hvm
- debo_ 3y agoTrue. In this case the author is speaking specifically about OCaml, which is quite comfortable to write imperatively. I think the thing that tickles most people about Rust is the thoroughness of the type inference and type checking, which one generally gets from any ML.
- vlovich123 3y agoYeah but traditional ML languages are ass slow. If I want to write applications that are that slow, why not use something that targets the browser or something like Ruby or Python that has a much deeper ecosystem to leverage? Unless you’re just playing around with building a language and trying out different ideas or you think it’s better at some other axis (eg lower defect rate). But I’ve generally found that my incident rate for bugs doesn’t change with languages. What rust has done successfully and “ML” languages have not is how to engage a broader community by exploiting a weakness in some class of problems they can do better on that existing languages cannot. That’s why they shifted into systems languages. Memory safety in systems languages was critically important and ML languages are typically too slow and memory hungry so Rust adopted some of the C/C++ communities obsession with speed and memory usage philosophies while allowing for much safer code to be written. I think it’s safe to say they’ve finally dealt a “COBOL” like blow in that completely new code being written is unlikely to be C++ and C++ developer salaries will keep going up because it’s a difficult niche. will take a few decades to play out because C and C++ is so heavily entrenched. And who knows, maybe the memory safety efforts the standards body is taking will help but I think ultimately it will only be useful for hardening existing codebases but that’s a temporary patch on a bleed. Rust used that initial wedge to jam in a code repository, making testing and benchmarking easier, etc etc. some of the Rust libraries are insanely high quality and waaay easier to work with than in C++ land (even at Google and Facebook where it was a record level of easy)
- cmrdporcupine 3y agoI kinda just think of Rust as C++ with a vaguely MLish inspired syntax & some aspects of the type & module system -- which is sort of how it explicitly started TBH -- and the borrow checker just grew out of what you end up needing to do if you rip the garbage collector out of a language like that.
- trealira 3y agoRust was originally garbage collected. From Graydon Hoare's post, he didn't want explicit lifetimes originally either. He agreed because he thought they would always be inferred by the compiler. https://graydon2.dreamwidth.org/307291.html https://graydon2.dreamwidth.org/307291.html It seems to me that Rust evolved into a better C++, but it did not start out that way.
- iopq 3y agoEarly on I argued for bignums and decimals in Rust, imagining also things like automatic currying, and other FP features But it went a completely different way, ripping out any runtime it had, making reference counting a lot more noisy, etc. It would be interesting to build a "simpler Rust"
- unstruktured 3y agoI like both OCaml and Rust, especially OCaml, but even after 2 years of Rust I'm still way more productive in OCaml. Unless it's absolutely essentially, I really don't want to have track the life times of my objects so explicitly. Thus, Rust for me serves mostly as a C/C++ alternative. I don't plan to ever write anything in C again, and even if it's not my goto language otherwise, for that I'm thankful.
- satvikpendem 3y agoI just clone and don't worry about it, works well.
- LAC-Tech 3y agoAlways felt zig was a lot closer to ocaml than rust was.
- hardwaregeek 3y agoYeah my semi-hot take is that type annotations in functions is a feature not a bug. It forces legible interfaces (with the exception of nasty generics I suppose).
- satvikpendem 3y agoI think that is not a hot take at all, haha. For some reason, in TypeScript people like to have return types inferred, and while I do that just because I'm lazy in my own projects, we enforce explicit return types in our work codebases simply because you never know when something might change in the function body.
- bmitc 3y agoBut they can just be auto-generated in documentation. It gets pretty annoying to type annotate everything for nothing but warm fuzzies when the compiler can figure it all out for you. Also, any decent IDE will show the types any way. The complaint about the types is just strange and shows a failure in getting Rust out of their head. Once you get used to F# and OCaml, then going back to a language that forces type annotations everywhere gets old and seems archaic because you spend all this time telling the compiler what it already knows (in a language like F# and OCaml). However, there are times in which type annotations are needed, especially in F# when dealing with objects. I generally type annotate when I feel the name of the argument doesn't capture the type. Writing F# often feels very Pythonic or Scheme-y, but at the end of the day, everything is being statically typechecked, so it's the best of both worlds.
- IlliOnato 3y ago> because you spend all this time telling the compiler what it already knows But a program should be optimized not for reading by a compiler but for reading by a human. This is essential for maintainability. The question is not how hard it is for a compiler to deduce types, but how hard it is for a human (and a human that hasn't necessary written this ode, and hasn't necessary read and remembered the whole codebase) to figure it out. It is not a simple question in general. Too much "infrastructure" clutter can hide the logic and intent of the code too. So to get the right balance in a language or in the code is non-trivial.
- kragen 3y agoi had some of the same problems; here's how i solved them, though keep in mind i am no ocaml expert, just a dabbler at some point i got sick of trying to debug mysterious compile errors about types in ocaml and started declaring types for all of my function arguments; ocaml lets you do that. an example is http://canonical.org/~kragen/sw/dev3/mukanren.ml http://canonical.org/~kragen/sw/dev3/mukanren.ml (an implementation of a tiny logic programming language, sort of like prolog with superpowers; cw inappropriate intimacy) where, for example, instead of writing let rec disj g1 g2 t = mplus (g1 t) (g2 t) i write let rec disj (g1 : goal) (g2 : goal) (t : state) = mplus (g1 t) (g2 t) which is more code, to be sure, but i think easier to understand because it's more explicit. and the compiler doesn't report your type errors in the wrong function anymore when you do that if you aren't sure what the types should be (easy when parametric polymorphism gets involved) you can paste the code without type declarations into the repl and see what it says in this case that looks like this # let rec disj g1 g2 t = mplus (g1 t) (g2 t) ;; val disj : ('a -> 'b stream) -> ('a -> 'b stream) -> 'a -> 'b stream = <fun> and in fact my `goal` type is more specific, `state -> state stream`, thus favoring comprehensibility a bit over flexibility (i can always go back and generalize the type more later) this makes the code a bit more verbose but most functions are more than five words long so it's not actually that bad maybe i should declare the return type too let rec disj (g1 : goal) (g2 : goal) (t : state) : state stream = mplus (g1 t) (g2 t) but so far parameter declarations seem like about the right balance declaring parameter types in your own code doesn't help with code you didn't write, but you can use the repl to find out what types the compiler inferred for it # List.map ;; - : ('a -> 'b) -> 'a list -> 'b list = <fun> presumably editors with lsp support have some magic keystroke to display this on demand in your editor or something, maybe an lsp user can tell us what it is
- dazed_confused 3y agoThere is a vscode extension for the ocaml-lsp that will show type annotations above each line of code.
- kragen 3y agoso, no keystrokes, but you can only fit half as much code on your screen?
- dangbackup 3y ago[flagged]
- sintheta 3y agoCan't comment on the comparison to Rust, but I recently spent quite some time learning OCaml, working through the excellent and freely available cs3110 course https://cs3110.github.io/textbook/cover.html https://cs3110.github.io/textbook/cover.html ; I really really wanted to like the language... I agree w/ the submitted article about the heavy reliance on linked lists and recursion, but what disillusioned me from it is that after many weeks of study I discovered competitive programming and on a whim started doing some easy problems. Since I was spending most of my time w/ OCaml at that point I thought it could also be a way to practice it further. So for a particularly easy problem it would go something like this: A straight forward, easy to read, performant C++ solution took me 10 minutes; an ugly unidiomatic OCaml version took me 30 minutes; and a beautiful idiomatic OCaml version using recursion that still no non-OCaml programmer could ever read took me something like an hour... It really dawned on me that after weeks of studying OCaml I barely knew how to write anything particularly useful in it from scratch. Dealing with user input/output still seemed cumbersome, given everything's immutability... from an intellectual perspective it feels interesting, and I had fun with it, but for now I decided I couldn't see myself becoming productive enough in it to justify further sinking time into it. There's so much to still learn for me (doing a network related project at the moment learning a ton about networking, sockets, etc), that trying to become an unproductive OCaml programmer probably shouldn't be high up my list of priorities... and that is not to say people aren't productive in it, that's to say that I don't see myself becoming productive in it any time soon, compared to being productive in C++ while being far away from being any expert in it. Maybe I'm too stupid for it, am not suited for it, need a few years of further programming to appreciate some things about it... but for now, sadly, I feel like I've wasted significant time I should have better spent on studying other topics and actually working on projects. And addendum: Never comment on downvotes, I know, but how I immediately got one for this comment... surprising.
- yodsanklai 3y agoNot all languages are equaly suitable to all tasks. If your leetcode easy problem is to write a quick sort, you wouldn't use linked list + recursion. That being said, the OCaml idiomatic solution is to declare a mutable array and write your for-loop, just like you would do in C++. > given everything's immutability OCaml has a lot of mutable datastructures. It's perfectly fine to use them. Even though with experience, you realize there's often a a better/safer way to do it. Also, I'm surprised that in 2023, functional programming still sounds like a mystery to some people. Python/Rust/C++ all have closures, immutable datastructures, their versions of the usual functional combinators, some form of variant types / pattern-matching (maybe not python?). C++23 even has monads. CppCon is full of talks of people realizing that they can write simpler code using this things. I believe all this FP tools should be in the bag of any proficient programmer.
- iopq 3y agoHorizontal scrolling code does make it quite impossible for me to follow the examples
- oconnor663 3y ago> heavy use of chained iterator methods in favor over traditional loops. This is one of the more intimidating hurdles for newcomers to [Rust], but after getting used to it, rarely will you see anyone write a for loop again. I think this varies a lot from person to person. Rust includes syntactic sugar for imperative code (like `if let` and `let else`), and personally I prefer to use that when it's not too much trouble. I find it's pretty rare that I reach for map/and_then/etc.
- ReleaseCandidat 3y ago>... linked lists are slow and inefficient with modern CPU caches, and you should almost never use them You should not use them most of the time in functional languages (like OCaml and Haskell and...) for this reasons either, it's just that (almost) all examples for beginners use them because they are "easier" than for example trees. Oh, by the way, OCaml does not have significant whitespace (but e.g.F# and Haskell do).
- dvektor 3y agoNotice the ! in "!significant whitespace" I thought I was being slick
- ReleaseCandidat 3y agoOh, sorry. I've read that as "non-lazy whitespace" ;)
- Jaxan 3y agoIn Haskell you typically don’t allocate any memory when using the default list type, because of laziness. The it’s totally fine and efficient. Just don’t use it to store data for later retrieval.
- momentoftop 3y agoYou use them all the time in Haskell and OCaml. Cache locality isn't such an issue. You're not mallocing linked list nodes. You allocate by a pointer bump of the minor heap, and if the GC copies your list into the major heap, it's going to copy the list elements together. You also use recursion all the time, and no, recursion is not generally straightforwardly optimised into iteration unless you're doing trivial tail recursion.
- ReleaseCandidat 3y agoYes I know and at least GHC optimizes lists better than most other data structures (I guess even isomorphic ones). But I guess your definition of "all the time" is not the samé as mine (yes, I use them in every project too). What I wanted to express is that lists are way less used than beginner tutorials may make it seem and there are other data structures available (like in other languages ;).
- grumpyprole 3y ago> Where are the types? OCaml supports type annotations practically anywhere, if you want to add them. Alternatively, you can use an editor that supports querying or showing types for bindings. VSCode and Emacs support this. > Remember recursion? How about linked lists? A lot of beginner material uses List to introduce algebraic data types, inductive and equational reasoning - but you don't have to use them. In fact, the standard library encourages Seq instead over List. It's a shame the author did not get past the basics, as there are many interesting comparisons to be made. For example, OCaml 5 effect handlers are a very interesting alternative to Rust's Async.
- lapinot 3y ago> In fact, the standard library encourages Seq instead over List. Uhu? Never heard about Seq before! Seems to have been introduced in 4.07 which was released 5 years ago, which is basically yesterday in terms of stdlib api. Set and Map, sure, but Seq? Lists are ubiquitous and they are indeed a central data structure in ocaml code. For anybody also wondering, Seq is a datatype for iterators (ie thunked lists).
- grumpyprole 3y agoThe Seq module has more functions than the List module. There are also more of_seq functions than of_list throughout. 5 years ago the compiler standard library was not a serious proposition, it is becoming more so now.
- yodsanklai 3y ago> the compiler standard library was not a serious proposition, it is becoming more so now. This is actually one of my grief with OCaml. I wish I didn't have to use Core. I don't look much at the std library anymore. My other grief is async (Lwt or Async). It forces to use monads which don't look very pretty in OCaml. That being said, I haven't looked at what OCaml 5.0 bring to the table on that front. This was my experience 5 years ago basically...
- 3y ago
- IshKebab 3y agoThe biggest issue for me with OCaml is OPAM. It's pretty awful. Simple things like "install this code" don't work reliably - often installing a different branch or just ignoring edits you've made, even after you discover the flag that says it should include them (it seems to be simply broken). It also doesn't work on Windows in any meaningful way. It's not just me that has these issues. We introduced a tool into my company that is written in OCaml and I ended up spending so much time helping people fight OPAM that I eventually implemented a build cache system so they didn't need to actually compile the OCaml code at all. Saved a bunch of hassle. It seems like a pretty decent language, aside from the issues the author of this article mentioned which are 100% true. But OPAM means I would never pick it. Similar issue to Python, which isn't really a bad language (I'd say it's mediocre-to-ok), but the packaging and tooling story is abysmal. More languages need to learn from Go, Deno and to a slightly lesser extent Rust which have all prioritised having a packaging system that doesn't make you want to tear your eyes out.
- ReleaseCandidat 3y agoThe biggest problém with Opam and OCaml on Windows is that most of the packages won't work. Officially, Opam does not support Windows at all, that is going to be included with 2.2 (which is still in alpha AFAIK) https://opam.ocaml.org/blog/opam-2-2-0-alpha/#Windows-Support https://opam.ocaml.org/blog/opam-2-2-0-alpha/#Windows-Suppor... I always thought of Opam as the best feature of OCaml (especially compared to Haskell with two no-working package managers - and no, Nix is a problém, not a solution either).
- IshKebab 3y agoYeah in fairness at least it has one package manager and it does seem well designed even if it doesn't actually work properly half the time. So it is fixable! Which is more than you can say for Python and C++.