Programming a space invader in OCaml and OpenGL: lessons learned
marmelab.com
marmelab.com
Also note that despite the "O" in the name, OOP in OCaml is rare (for instance, there isn't any in the code in the article).
Especially if you're targeting native, so all your dependencies are written and documented in OCaml syntax.
Runs with WebGL and OpenGL.
- https://esy.sh/ - a package manager that offers a lockfile, aggressive build caching, and reproducible hermetic builds. So when you come back to a project a few months later, it still builds.
- https://reason-native.com/ - an umbrella for useful Reason-oriented native libraries
Which is kinda sad, because it has one of the best OO type systems in the industry, in my opinion. The decoupling of inheritance and subtyping, in particular.
I'm not sure why the author decided to use such a complicated setup using Make and Docker.
They could have just ran `opam switch create 4.09.0`, which would have created the compiler, then `opam install ...` for the dependencies.
Still, an interesting article. I wouldn't recommending using OCaml/ReasonML for gamedev since getting it to work with Windows is pretty much impossible.
I find OCaml really enjoyable once I got going, but I do think the initial setup is a major barrier to larger adoption. I think a post on howistart.org might be useful, but it'd probably be polarizing since most functions have two major ways of doing it (Jane Street vs. not, Lwt vs. Async, etc.).
I talked about "confusion" because I suppose a newbie may quickly be confronted with this question, for intance if they want to follow "Real World OCaml" which is based on Jane Street libraries.
The tooling around OCaml has really improved a lot these last few years (opam, dune, core/async, merlin, ppx...). The downside is that it raises the bar for newcomers.
For a while, I grumbled that it had spoiled me for other languages, the "fun" looking stuff that everyone talks about online. But as with all... rustic... experiences, it's only fun for the weekend. I wouldn't want to spend my entire life chopping wood for fuel, either. Even after spending 3 years fully in JavaScript land, getting very comfortable with all of the tools, it never really got "good enough". Years of effort and my dev experience was still worse than a default VS install. So I went back and have been loving life again.
Docker because, presumably, he didn't want to dink with installing it locally.
Sorry, what is not straightforward in building with `dune`?
- How do I run tests? Oh, you can't really dune runtest on executables, only libraries. So how do I run tests on executables? Make a library with the core of the functionality and wrap it, I guess. Wasn't obvious.
- Tree-like heirarchy of dune files confused me since it wasn't really explained anywhere. I wanted my source files in a `src` directory; most of the examples assume your `dune` file is just hanging out with the source in the toplevel.
- Does `dune` handle dependencies (like I would declare them in a rebar.config, mix.exs, or a package.json)? Not really, it plays with opam, requiring one to read a bunch of docs for opam, and its files, and where they should go.
- The syntax of the dune files changed a bit from when I started the project (it was just moving from jbuilder -> dune). Less of an issue now, though I still question the use of s-expressions.
- Can I load an interactive shell with the definitions? There's a command in the documentation for it, but it didn't work quite right when I tried.
- If you look at other projects, you'll see they use opam files differently from one another.
- How do I add a dependency in my current switch (something like npm --save install <library>?) I tried `opam install --deps-only`, but it complained sometimes about the state of uncommitted files.
I understand why it's like this (trying to tie together a ton of use cases) and love developing in it after it's all said and done, but there are a lot of rakes to step on.
It's explained in project layout [1]. And it's not a tree like, it's basically flat, since it doesn't matter how deep in the file system your dune file is buried.
It's like `meson` in its concept.
> Does `dune` handle dependencies
It doesn't, and so neither do `meson`, `cmake` and most other build systems. As you may want to use various ways of installing dependencies: native packages, manual compilation, opam, esy.
> Can I load an interactive shell with the definitions?
Yes, you can run `dune utop ./path`
> If you look at other projects, you'll see they use opam files differently from one another.
Dune is independent of opam, opam file is needed to publish your package to opam. You can specify packages with dune without creating an opam file, see [2]
> I tried `opam install --deps-only`, but it complained sometimes about the state of uncommitted files.
If you use opam, you can do `opam install --deps-only`, or `dune build @install`, which would suggest a command to install missing packages.
[1] https://dune.readthedocs.io/en/stable/overview.html#project-...
[2] https://dune.readthedocs.io/en/stable/concepts.html#package-...
(Just to avoid accusations of <whatever>: I actually discovered FP through OCaml and wrote a few early-ish libraries in the ecosystem, but then discovered Haskell... and the rest is history.)
Probably never going to use OCaml again (just for business reasons, nothing against it per se), but good info for other readers.
EDIT: I also hope some of the OCaml'ers did get some of use of the things I wrote. I'm not going to doxx myself by naming the exact things, but just wanted to say... I hope you took advantage of them because the stdlib was quite anemic, so you might want to extend it. They were just missing bits of the ecosystem that I wanted to be more convenient... and I implemented a data structure paper or two.
Having learned ML via Objective Caml's predecessor, Caml Light, ReasonML seems to be just sugar coating for those with aversion to ML classical syntax.
That’s exactly what it is, no secret about that, that’s the goal of that project.
Programing isn't poetry.
Or Java and Kotlin / Scala.
Depending on how well an alternative syntax is requested and adopted by the existing ecosystem surrounding the particular VM, YMMV. Without knowing, I think you get quite different answers in each case.
Focus on platform languages and you always win long term.
Talk about moving the goalposts...
(Yes, TS adds new grammatical constructs, but grammatical constructs are about grammar, not syntax, contra Reason).
¯\_(ツ)_/¯(Admittedly, F# already shares a couple Reason ideas like C-style comments instead of OCaml's traditional ones. So the waters are clearly muddy here to begin with, but a lot of the big things like let-binding scopes and ;; versus ; presumably would be big F# requests if Reason was so strongly superior to the inherited OCaml legacy.)
Probably writing C#.
If OCaml had Erlang's runtime (parallelism and concurrency via actors with message inboxes) it would likely be the most used language.
Its lack of adoption isn't at all related to syntax IMO. Programmers use all sorts of quirky syntaxes and the world hasn't ended yet.
I agree that those things are pain points (I said as much upthread), but clearly the language is also part of the problem or else there would be no interest in Reason.
> As a guy who tried to get into OCaml development several times, the language is not the problem at all. It's the several different "standard" libraries, lack of good official agreed upon tutorials and guides, and the fact that it has a GIL and is essentially single-threaded.
A big reason Erlang is similarly unpopular is its syntax as well (many of the most popular languages are dynamically typed, so we know it's not the type system). This theory is supported by the relative popularity of Elixir, which shares Erlang's runtime and which has enjoyed a fair amount of popularity in a relatively short time despite introducing a number of syntax- and non-syntax-related quirks. OCaml on BEAM would certainly appeal to the intersection of FP and static-typing enthusiasts, but it would not remotely be popular.
I realise what I said is not an undisputable truth and I wouldn't argue about it. I probably should have just said that I am observing an almost global strive for dev teams to be able to catch bugs earlier and to have a bit more guarantees by the mere virtue of the program successfully compiling. But it might be my filter bubble.
This is why I said an OCaml with the BEAM guarantees would likely rise very high; because my observations lead me to the belief that people want more compile-time guarantees. And yeah, that might be a skewed observation.
I think this is a pretty broad pattern, but:
1. It's not happening in isolation; there's also a move toward greater development velocity
2. There's lots of low-hanging fruit to be had--lots of people can get tremendous safety benefits by moving from languages like JS and Python to Go or TypeScript or typed-Python even if they don't have as elegant a type system as OCaml (and those alternatives don't require the same tradeoffs as OCaml or even an OCaml on BEAM).
> This is why I said an OCaml with the BEAM guarantees would likely rise very high; because my observations lead me to the belief that people want more compile-time guarantees. And yeah, that might be a skewed observation.
Like I said, I don't think it's a skewed observation, it's just that there are other ways of getting most of those compile-time safety benefits with fewer tradeoffs. But I would be a huge fanboy of an OCaml-like type system on BEAM (especially if it can have Go- or Rust-like attentiveness to practical software engineering concerns).
https://github.com/jashkenas/coffeescript/
Its not as popular to be certain, though.
They are just "dead" in that they are specialized programming languages with a domain of their own, that magpie developers don't find attractive to put on their CV.
CoffeeScript in 2020 is just legacy code that one wants to get rid of, because some juniors found it cool to smuggle into the IT infrastructure.
Opinions are fine, I don’t like CoffeeScript and never have.
It’s no justification to shame someone’s choices when perfectly well working code is usable via what appears to me to be a healthily maintained tool. Less popular? Sure. Would I reach for it to start a new project? no.
Calling it dead though and saying it’s only around because it’s “legacy” and only was used by “cool IT juniors” is shaming a legitimate choice without giving any context or data to back up the assertions
Of course all of this is highly subjective, but my understanding is that it's important to consider if you want highly successful projects.
I think this is very much domain dependent. Generally the more important efficiency is, the less I find this to be true. And really, code is always for the machine at the end of the day.
Additionally, if an added code layer makes the code more difficult to debug, that's also making the code worse for humans. Note I'm not arguing for or against whether that's happening in the case of OCaml/ReasonML, just making the point.
On the other hand, code is read much more than it is written.
You may also regard efficiency as a necessary evil if the cost is readability.
Ultimately the winning strategy in this regard is achieving "free abstraction", making code that is both readable and efficient. Different languages aim at this. C++ has been best at this for most of history. Haskell and Rust are competing at this now as well.
OCaml isn't exactly efficient because of free abstraction, but because of its extremely simple translation strategies. OCaml's "flambda" compiler extension wasn't released as stable until 4.03 (2016), which means that before that, very little high-level transformation was happening.
My experience is that OCaml programmers care about low-level optimization, for good and for bad.
For example, in this StackOverflow response there are two examples of a function 'partialsums':
https://stackoverflow.com/questions/37694313/make-ocaml-func...
The one that Jeffrey Scofield provides is essentially more efficient because the accumulated value is a list, just like the end result, whereas my solution accumulates a tuple, which means every iteration of the fold involves boxing and unboxing that tuple. An optimizing compiler working at a higher level of abstraction might figure that out and re-use the memory of the tuple, but no sir.
Maybe if Go didn't start as a hobby project inside Google, then they /might/ even have seen some opportunity in Ocaml / Standard ML, but now that Go exists, and has a quite good toolchain, the incentive is low. Go and its foundations are radically different from a FP language like OCaml though.
[1] Yes, humans used to write machine code. By hand. Computers were for serious work, not mere clerical tasks such as translating mnemonic order codes to binary and doing address arithmetic.
First time I've heard a C-like syntax described as "poetic".
But the accumulation of @-signs, for example, seems to be a bit much.
The talk where he says this: https://youtu.be/5fG_lyNuEAw
So it's the Elixir to Ocaml's Erlang?
For people like you and me, we like the ML syntax and don't have too much need for a new one. But for many others, this may be the only way they will ever even decide to look at an ML. And hey–if after that they decide that they actually like the ML syntax better–well that's one of the 'secret' goals of Reason.
The first bad part is that Ocaml does not have a language spec. The implementation is the spec. This puts them at the whim of the Ocaml devs with no other options. SML is a standard with several good implementations.
Ocaml requires different operators for float and integer arithmetic. SML assumes an integer type unless you include a decimal. Both need to be replaced with module typeclasses (and both are working in that direction). For SML though, your existing code can continue to just work as it did before while Ocaml will be left with lots of `+.` or `*.` operators everywhere.
Another mistake that bleeds through is mutable strings. This is an outgrowth of all arrays being mutable. Immutable strings allow efficient representation as ropes instead of normal arrays (along with other optimizations that are good for GC'd languages). SML has immutable strings and vectors along with arrays (which you can still use as mutable strings of characters if you need them).
For their React-like goal though, Ocaml's lack of anonymous record types is the absolute worst. In SML, I can just type up a record and pass it in (no type definitions required). When passing in stuff to a component, you have to have all these named arguments. In SML, I'd just pass an anonymous record and destructure it in the function -- Just like in Javascript, but with types behind the scenes to keep everything straight.
Writing a custom parser for Babel like typescript or coffeescript have done would have been better for the language design and independence.
OCaml strings are immutable by default since OCaml 4.06.
>For their React-like goal though, Ocaml's lack of anonymous record types is the absolute worst.
The object system actually allows this to be fair & it does get a little bit of use in some Reason libraries targeting React developers
I get that SML has advantages over OCaml but I don't really get what you're trying to argue for. Clearly if you like SML you'd prefer OCaml to JavaScript...
[1] https://caml.inria.fr/pub/docs/manual-ocaml/language.html
The title ("Programming A Space Invader In OCaml and OpenGL: Lessons Learned") still fits with the content, don't you think?
https://github.com/marmelab/react-admin
I'd love to see an official Marmelab adapter so I can build my admin interfaces with ReasonML.
> And although some tools exist to make our life easier, they are poorly documented
Seems like author choose something poorly documented. Dune build system [1], opam [2] and esy [3] package managers are very well documented.
[1] https://dune.readthedocs.io/en/stable/
1) places features diverged
2) modules
3) F#'s "light" syntax mode
None of these really seem like Reason at all. Do you have any specific places where they're similar?
Submitted title was "Is ReasonML good by itself or the underlying OCaml the source of awesomeness?"
dnf install ocaml ocaml-lablgl-devel
?Anyway here's a toy game I wrote in GL + ODE + OCaml years ago: https://github.com/fccm/OCamlODE/blob/master/examples/katama...
Once you feel you have that sussed - it shouldn't take too long - pull up ReasonML and give it a go. Use the Bucklscript-Tea library - the architecture will be familiar from Elm.
Then give Reason-React a go.
Once that itch has been scratched move on to PureScript for a whole new rabbit hole to fall down.
Reason still hasn't had its async story ironed out, and has poor support for Unicode[0]
[0] "ReasonML strings are encoded as UTF-8 and not compatible with JavaScript’s UTF-16 strings" (from https://2ality.com/2017/12/basic-types-reasonml.html)
Reason's async story is pretty simple: use promises until the let-syntax (like generalized do-notation) lands, then use that.
> "ReasonML strings are encoded as UTF-8 and not compatible with JavaScript’s UTF-16 strings"
Unfortunately this is inaccurate, and you left out a significant detail from the article. Let's go point by point here:
- Reason strings are not encoded as UTF-8, in fact they aren't encoded at all.
- But, when targeting JavaScript using the BuckleScript compiler, strings can contain Unicode characters and use the JavaScript runtime's support for Unicode.
- This is not a short-term workaround, but the intended solution.
ReasonML/Ocaml is a general language. You can use whatever framework and platform you like. There is an Elm framework for Elm like development, there is a React layer for coding against react. But you can also write server apps, command line apps, GTK etc..
The main difference is, Elm is like a drastically simplified version of Haskell, while Purescript is essentially Haskell with benefits (row polymorphism).
If the goal is some kind of syntax on top of JavaScript, I have no opinion.
I thought the ocaml stdlib was relatively small though?