Hacker News new | past | comments | ask | show | jobs | submit login
Oxidizing OCaml: Locality (janestreet.com)
246 points by amatheus on May 27, 2023 | hide | past | favorite | 66 comments



I've been listening to Signals and Threads podcast, and I remember listening to this episode - https://signalsandthreads.com/memory-management/

(I don't know neither Ocaml, nor Haskell, or any ML language), but the podcast is always fun to listen to (not only programming also)

Now would relisten this, and may actually understand it!


I'm curious, which episodes are describable as "not only programming"? I ask since I can't think of one.


I have not been listening to them in the past but stumbled upon this episode "Multicast and the Markets" and I feel this has a lot of stuff about hardware and network than the software. Highly recommend to check it out.

https://signalsandthreads.com/multicast-and-the-markets/


You are probably right, wrong recollection then, or it might've been an episode where some time was spent on talking about other important points - culture, life, etc.

Podcasts (not only this one) have evolved a lot! (another fav of mine is "Go Time" and many others).


There was a good one about NTP and microsecond precision that had programming but was more a discussion of how hard time is to get right at great precision.


This is a great idea, but I have questions

* Is there a process for upstreaming this into the mainline language, or is this essentially JaneStML now?

* Why choose such an obscure word as `exclave` to indicate return-value optimization? How about `return local` or something similarly approachable?


Our long term aim is to upstream all of our work from our branch of the OCaml compiler. Of course, that is contingent on the ideas we’re developing there being accepted by the community. There are two main reasons we work on our own branch:

1. Language design is hard. At Jane Street we have a great opportunity to design new features, test them extensively in a realistic environment, and then change them. Because we have access to the entire code base where the features are deployed, we can even change concrete syntax relatively easily. So by developing internally, releasing internally, and then upstreaming with experience, we can be more confident that the feature design is correct.

2. We get a faster turnaround between idea conception and internal deployment. Working solely with upstream, we would develop an idea, go through a long design discussion with upstream, implement, merge, wait for release, wait for the rest of Jane Street to be ready to upgrade, and upgrade. Now, we can implement an idea in parallel with its design, rolling it out internally in stages (as appropriate), and then upstream later. This is a big win for us, and well worth the extra time spent moving changes back and forth.

> Why choose such an obscure word as `exclave`

We discussed a lot of possible choices and eventually decided this was the best one. I personally think names should either be self-explanatory or memorable -- so that once they have been explained they aren't forgotten -- here we went with memorable. As a word, exclave also captures what is going on in terms of part of the parent region being contained within the child region. `return local` was a strong contender, but it implies that `exclave` is always about functions -- whereas the actual feature is a bit more general than that. It is also a bit easier, when adding new keywords, if you pick a word that isn't used much, and `return` is used a lot.


This process seems to intentionally cut off upstream from having input into the design of the features during development when there's still a chance to change it. What happens when you implement something that you like, release it internally, but then it turns out upstream doesn't like it or wants it a different way? Will you maintain your feature on your fork forever (thus JaneStML), or will you abandon your feature? Is the thought that, honestly, this will never happen given Jane Street's track record in the community and the level of battle testing that this process brings to the design process?


Another possibility is to change the design based on upstream feedback and adjust the internal codebase to it.

By parent’s description, those codebase adjustments are done during the iterative process of feature development too. Upstreaming would just be the last iteration.


As someone from the community, I absolutely love this design (and blog post). It would be especially great for returning options, tuples, results, etc. with no overhead at all.

I suppose it could also be useful to allocate int64, floats, and maybe even bigints on the data stack? I suppose it's more difficult to put C blocks like bigints or bigarrays in a region.


> should either be self-explanatory or memorable -- so that once they have been explained they aren't forgotten

For years I've had a pet, personal theory on the hierarchy of naming; and these were my first 2 "levels". I never heard anyone else ever blurt it out in these terms. (My thought was that I was weird, or it was just so obvious that no one ever needed to.)


I was reading the post and thinking 'glocal' :-D


They always had their own version of the compiler: https://github.com/ocaml-flambda/ocaml-jst/tree/main

Many of their changes have been ported to the 'official' OCaml compiler.


Exclave is also a (PPX - that's a kind of preprocessor for generating the actual code) keyword: `[%exclave]`. Using this avoids name clashes.

See: https://ocaml.org/docs/metaprogramming


Using obscure works makes some people feel smart.


Explain std::launder then - https://en.cppreference.com/w/cpp/utility/launder - it's simple word, mostly used when talking about dirty money ;) - but hey - I've still no idea how/when/why to use it...

;)


Actually the word "launder" seems to be quite to the point given what this does.

I'm not a C/C++ developer and I have to admit: For me reading anything about those languages is every time just mind-bending! (And I have a hard time not to use any curse words right now in addition).

It took me over 20 minutes to understand (more or less) what this `std::launder()` does. The spec is just gibberish! So I've tried a BS generator a.k.a "AI" than[1]. Together with a Stackoverflow entry[2] it finally made click.

This function "launders" pointers from compiler assumptions. It will give you a "clean", pristine pointer back, without for example the information about the concrete object attached to it previously (because for example casting an object to its base class won't give you a proper pointer to the base class; at least that's what as SO comments says… Sounds quite ill, but so is C++). It will also for example "launder" away compiler assumptions about the `const` property of an object pointer. It's needed for example when you do "in-place replacement of an object" (replacing directly the bytes in some memory area of another object). Just casting won't make the compiler "forget" where this memory came from / to which object it was once attached. So you need to launder the pointer to the new object so the compiler forgets what it assumed previously about that memory area.

But let's not talk about what it actually means when a language needs some mechanism like that… My "WTF per Minute" output just reached a new all time high, I guess.

[1] https://www.phind.com/search?cache=91e3f5ce-dde2-422e-a3eb-6... [2] https://stackoverflow.com/questions/66176720/why-introduce-s...


Holy - the explanation you provided clicked for me! Thank you!

And I should use now `phind.com` - awesome!


Using simple words in confusing contexts also makes people feel smart. Essentially any way of being technically correct in confusing ways has this effect; "I'm so much smarter that you can't even understand _why_ I'm right" is too juicy a flex for some people to avoid.


https://twitter.com/ciura_victor/status/1662536472342192128

"Rinse and repeat That’s my std::launder joke "

best answer I've got so far :)


I believe this is an explanation (part of) of this proposal:

https://github.com/ocaml-flambda/ocaml-jst/blob/main/jane/do...



Yes - upcoming posts will cover the uniqueness and data-race-freedom designs.


OT: Funny, I start to see here more and more Heise expats. Welcome @ReleaseCandidat! :-D


My understanding is that Go does a similar kind of optimization (where locals that don't escape don't go on the heap) but it does it automatically. This has the positive that you don't need annotations but the negative that you can assume some code is benefitting from the optimization when it isn't and there's no warning about it.

I'm curious whether the authors here considered this. I didn't see any discussion of related work in the post.


From the post:

> Even without explicit mode annotations, the compiler can statically determine which variables may escape their enclosing region. Such variables are assigned the global mode; all others are automatically inferred to be local.

There's a little more info about mode inference in the proposal:

https://github.com/ocaml-flambda/ocaml-jst/blob/main/jane/do...

So arguments to "public functions" do require explicit annotations in order to be local, but otherwise the compiler is able to infer locality and this acts as a transparent optimization.


My reading seems to be that the compiler can and will do it automatically.

The annotations make enforcement. It's easy to make the compiler put something on the heap by accident.


This the best way, productivity of automatic memory management, and low level features for when that extra performance actually makes a difference.


I’ve been critical of OCaml, but I gotta hand it to them, it does feel like the language is going through a renaissance. It’ll be interesting if 5-10 years down the line it manages to significantly improve the usability story while also adding on modes and unboxed types. I’m more focused on usability but these features are compelling too.


Can someone explain to me why Jane Street - a trading firm - is so all in on OCaml?

There's lots of other choices that would be easier to recruit for, with wider community and industry support.

Where does the Jane Street commitment to Ocaml come from?


I think it started off because they needed more speed than the higher level languages like ruby and python could give them (at the time), but couldn't justify writing in a low-ish level like C/C++. I suppose they might have landed on something like Pascal or Fortran, maybe even a lisp, if they'd hired a different developer who had to make the search.


It lets them hire the types of people excited by Ocaml.



My recollection is that their systems used to be based on Excel (in the very beginning). A new hire (Yaron Minsky) started using Ocaml for some research project and it stuck.


Is Ocaml actually good or is it a meme that people use it and become obsessed with it. How is the transition if you mostly code imperatively? Are there things that are not ergonomic for it to do like GUIs or games?


I think it was Steve Klabnik who said that it's basically the version of Rust with a garbage collector that people ask for. Might have been this episode of Oxide and Friends: https://oxide.computer/podcasts/oxide-and-friends/1208089


Rust was originally described to me as “Rust and C have a baby,” so yeah I’ve been a fan of this kind of thinking for a long time.


Rust is its own father? I think the borrow checker will make this difficult.


Lol, oops: I meant OCaml, if that wasn’t obvious.


OCaml is quite literally a parent of Rust since the original Rust compiler was written in OCaml. (I know you know that; I'm just highlighting how apt the analogy was. :)

https://github.com/rust-lang/rust/tree/ef75860a0a72f79f97216...


It was, but it was hard to resist commenting on the self-referential nature of it that Rust often has problems with.


Just need support for recursive lifetimes


Serious question: How would that work?


What do you mean by “that,” specifically? A bit hard to know what exactly you’re asking.


Recursive lifetimes. The idea of the gp.


Oh gosh, so I had seen your comment on mobile, and couldn't tell who you were replying to. I thought you were asking about "OCaml and C had a baby."

I have no idea, maybe your original parent will elaborate :)


I don’t understand the confusion. It’s all right there, on page 404 of the rust spec:

> Parameters with a recursive lifetime can only be used in a tail-call position within a function. If a function with only recursive params compiles successfully, it is guaranteed not to grow the stack.

For the self-recursive case, people are likely better off just writing the imperative version. But using these with mutual recursive calls lets you build safely build state machines that compile down to the same performant code a classic goto-based implementation would in C.


Sounds reasonable.


I joke with people that F# is “easy-button Rust”, so that also tracks lol


Nowadays my primary concern is readability and understandability. Thrown into a codebase, can I understand what's going on?

OCaml (which at FB was used to write the Flow typechecker and Hack compiler afair) favored higher order functions. I enjoyed playing with these in my Haskel-based introduction course at university, and I even wrote a similar language that favored them.

But I now heavily dislike if `a b c` means "call to a with functions b and c with unknown arguments". I need to read all three (a, b, c) to really get what's going on.

This was my biggest gripe with OCaml as it was used at FB.

(purely syntactically compare this to `a(parent => b(parent), child => c(child))`, for some verbosity I get a clearer syntax - a is the callee, and I got some information about what b and c possible do - this is a contrived example of course).

I think the strength of ML and Haskell got eroded as more "mainstream"y languages got decent type systems (TS/Flow, Hack, Kotlin, even Rust, Python is getting there).


I'm not sure your example makes sense.

`a b c` means `a(b, c)`. MLs just leaves out the parens.

You can't know just form looking at `a b c` what `b` and `c` are. They may be functions of course. But also any other value.

This would be exactly the same in for example JS. Nobody in JS ever complained that you can pass functions as arguments to functions and "don't see the arguments". (If you would write arguments you would actually call the function, and not pass it as a value.)

But in OCaml you have a static type-system that can tell you at any point what `a`, `b`, or `c` are! Just hover the symbol.

> I think the strength of ML and Haskell got eroded as more "mainstream"y languages got decent type systems (TS/Flow, Hack, Kotlin, even Rust, Python is getting there).

I don't think so. Especially given those examples.

TS/Flow doesn't have a sound type system. It may "type-check" fine and than explode at runtime. That's imho worse than dynamic typing as with dynamic typing you know at least that such fuck-up will happen. But TS delivers a false feeling of security about type safety where there is none.

Hack is "gradually typed" which is just another word for dynamically typed with some static checks. So not even close to MLs.

Python is also still dynamically typed, and that won't change. The bolted on type-checkers are unsound. So same situation as with TS.

Nothing can be said about Kotlin afaik, as there was no formal work on its type-system.

And Rust? Rust is a ML (with ugly C syntax)! So Rust is a prime example of how MLs are still one of the most modern and influential language designs to this day on.

With Rust and Scala you have even two MLs in the Top20 languages. If something than ML is slowly but finally approaching mainstream. Maybe not in its original form but still the tradition lives on.


I agree with you on TS being unsound. Flow on the other hand strove for soundness, as does Hack. Unfortunately TS dominated thanks to good strategy and some better ergonomics for open source (which were not a focus for FB).

Rust is more ML than Flow or Hack because of traits (type classes? Not sure what ML calls them or if it even has them, Haskell does). But it’s a vastly different language otherwise, so I think the “other languages have learned from ML” is more accurate description of what’s happened.

Some people write JS/TS in the ML style (point-free), but it’s not by far the most popular way. TC-39 came around to Hack-style pipelines for the same reason. For me the stylistic difference is a crucial indicator of preferences and priorities. Haskell’s operator “overuse” lives in a similar world.


Yeah I think it’s kind of like how pg wrote about how using lisp gave him a disproportionate advantage. Sure, when people used fewer dependencies and the alternatives were C++, Python v1, and Perl, but these days a lot of languages have Lisp features and the cost of using a niche language is worse dependencies and tooling.


PG’s disproportionate advantage was productivity - despite LISPs disadvantages - which he writes about in depth here: http://www.paulgraham.com/avg.html I suspect that productivity gap would still exist because the primary productivity feature is macros, which are still either uncommon or limited in other languages:

  The source code of the Viaweb editor was probably about 20-25% macros. Macros are harder to write than ordinary Lisp functions, and it's considered to be bad style to use them when they're not necessary. So every macro in that code is there because it has to be. What that means is that at least 20-25% of the code in this program is doing things that you can't easily do in any other language.
So why doesn’t LISP rule the software universe? I presume it is because it takes exceptional skill and taste to use macros productively. I also suspect it requires a small team because everyone needs to deeply understand many macros which are unique to the system developed - deep customisation has its costs. Certainly I have worked with plenty of programmers where I was pleased they didn’t have access to macros!

PG has created two other languages (ARC and Bel) which shows a high level of skill, and perhaps also shows that LISP is not his ideal language? He wrote this about designing a programming language: http://www.paulgraham.com/popular.html and he writes about the history of LISP here: http://www.paulgraham.com/icad.html which also talks about productivity:

  if you were to compete with ITA and chose to write your software in C, they would be able to develop software twenty times faster than you [by using LISP].
  ITA's hackers seem to be unusually smart


Readability is very subjective and also people get used to syntax. Elm is widely considered to be one of the most readable syntaxes by its users.


Disclaimer : I didn't try Ocaml (weird numeric operators turned me off of it a decade ago) but I did use F# which is supposedly similar.

But I don't know what you mean by imperative - for loops and stuff ? Most languages these days support functional programming concepts. Unless you're coming from C it shouldn't be that groundbreaking.

Note that Ocaml isn't lazy by default like Haskell - now that makes things... interesting I guess.


You could always try it out and see for yourself :-)

Here's a pretty good article about the transition: https://roscidus.com/blog/blog/2014/06/06/python-to-ocaml-re...


Interesting read.

What I would like to know is why they didn't take more direct inspiration form the "ML with Python syntax", namely Scala 3, and its "capture checking"[1].

Those OCaml "modi" look just like special cases of capture checking to me. Was that more general approach considered? And if, why was it dismissed?

[1] https://docs.scala-lang.org/scala3/reference/experimental/cc...


Found this video too which is worth watching - https://www.youtube.com/watch?v=yGRn5ZIbEW8


Seems like nim could be a good replacement? GC when you need it and explicit control when you need that.


Not for Jane Street. They likely have millions of lines written in OCaml and rely heavily on its type system safety guarantees.


From the article:

> Ideally, a language could provide a spectrum of allocation strategies freely interoperable within a single application. With modes, users can write OCaml with all the usual GC guarantees—but when performance is paramount, opt into the consideration of lifetimes, ownership, and concurrency.


Nim is such a nice language, I would really love to see some high-visibility corporate adoption for it.


I think it is not getting enough usage because a lot of people love that others should adopt it first.


I can sneak it into a few scripts here and there at work, and I can write my side projects in it, but there's only so much I can do.


where it fits? it feels languages are alive as soon as it fits somewhere. rust and zig for example. seems roc or bosque too. go and ts had fit into some industry slot.

where nim fits? how it can compete with other new fits and other old?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: