Reason ML toolchain
khoanguyen.me
khoanguyen.me
That said, I recently tried rewriting some code, specifically a canvas-based game, in Reason, and while the language itself was amazing, the interop with the mutable JS canvas API was really annoying. To be honest though, not sure if games is what Reason is meant for so it's not a big deal. It's a great fit for React, which really leans heavily on the functional paradigms that Reason promotes.
Also given that FB has rewritten 50% of messenger in Reason, I don't see it dying any time soon. So here's hoping that it keeps growing and the tooling improves. I'd definitely like to see it become more beginner friendly, and additionally, I'd like to better understand how to interop with existing OCaml libraries and target the native platforms.
I saw some people wrote games with Reprocessing, maybe this helps.
Here is a successful story [1] of using reprocessing for making cross platform game (Android, iOS, macOS and Web) from Jared Forsyth
[1] https://jaredforsyth.com/posts/making-a-cross-platform-mobile-game-in-reason-ocaml/[0] http://fable.io/
If anyone has any advice on how to help this I am all ears but writing garbage interop code gets old.
One way to look at it is immutable.js minified is 55 KB. That's about 5,000 lines of JS. If you're using a functional language, you've gotta bring along those immutable datastructures somehow, and 5k lines of Clojure is gonna output greater than 5k lines of JS.
JS favors arrays, OCaml lists, so you either write dsls with [| |] (ugly), or you import a module and convert from lists to arrays and your bundle size goes up 80 KB (don't recall if minified or not).
I was surprised to see Reason continue with this syntax. The idiomatic approach for either side is suboptimal unless I'm missing something.
One reason to favor lists over arrays has been that they are immutable. Most of my personal JS array usages have been as dynamically sized arrays, because I’ll use .push all over the place. That usecase is the primary reason to use lists instead: it’s much faster to prepend and destructure on lists.
div [ id "test" ] [ text "stuff" ]
vs div [| id "test" |] [| text "stuff" |]
If the JS library expects an array (not uncommon), that gets ugly quickly.In my experience, the crux of the difficulty actually comes from the JS APIs themselves working in a very JS-way, to be nice. Lots of DOM APIs rely on the dynamically typed nature of JS, making it super hard (probably impossible) to make statically typed bindings to.
Coming from JavaScript / React / Redux / Flow, ReasonML initially looked more familiar, but I still found Elm easier to pick up. Elm has a more unified feel. ReasonML read to me as an assemblage of components — each of them high quality, and expertly integrated, but it still felt like more different pieces all to learn at once. YMMV.
OTOH, you can't (currently) use Elm to write a native mobile app.
Also, that bundle size is still smaller than react + react Dom. And you get the features of redux and immutable for free plus a solid, statically checked type system.
What they are cleverly leveraging is the twenty years of solid type theory (a sound type system), language design (more powerful pattern-matching, functions, syntax sugar), and engineering practice (the OPAM ecosystem) that has gone into OCaml.
This is why Reason/BuckleScript are able to punch above their weight class.
At the end of the day though, I don't understand the appeal. The people who really are into functional programming will gravitate towards GHCJS and PureScript whereas the ones that aren't as into it will be drawn to TypeScript and plain JS. I really don't get where Elm fits into it all.
I can see Reason being way more mainstream.
Fundamentally what appeals to me about Elm is the design sensibility of "what is the minimal set of primitives necessary to achieve a great user experience?"
Over the years Elm has gotten simpler and nicer at the same time. The last three major releases removed language features.
I love that! Building software is complex enough as it is; one of the things I like most about FP is that I no longer have to spend time thinking about objects, classes, inheritance, etc.
It frees up more of my brain to focus on building things!
The Elm architecture ported to Bucklescript.
1. https://www.codementor.io/rudolfolah/elm-vs-react-developmen...
Hello world in that Haskell library was 1 MB last time it was posted.
It’s virtually impossivle to get shouldComponentUpdate() right when you have ideomatic react with callback props, since callbacks can’t be reliably compared for equality.
Unfortunately, what you appear to have currently is that a lot of the tools and utilities you would want to use in ReasonML are written in either JS of Ocaml, and reply on the interop when you target that platform. Want an ORM? There's a couple, but they are all targeted at Ocaml, so you either compile to native and link or try to port the Ocaml source of it to ReasonML (maybe automatically?) and maintain it. Want a web router/framework? There's a lot written in JS, but you'll be configuring and writing some JS most likely to get it to work, and no compiling that natively. This is a recurring theme. Most robust larger packages are in one language or another, which causes problems for anyone wanting to leverage the inherent advantages of both target formats.
What it seems to come down to is that Reason needs a whole lot more software written in Reason so it's more flexible. Being able to use Ocaml and JS packages is a feature and liberating, nut having to use Ocaml or JS packages ends up putting constraints on your project. ReasonML has a lot of promise, but at least for what I was excited about, it can't deliver. Yet.
I think I failed to explain about this in the article. OCaml and ReasonML is the SAME language with different syntax. You don't need to worry whether the library is written in OCaml or ReasonML as long as your build tool support both ReasonML and OCaml.
Right now it's 90%+ Perl, which I'm for the most part happy with, and the rest is JS, which I'm not exactly thrilled to deal with (Perl and JS are similar enough in some respects to make the parts of JS they flubbed really annoying). The main draw of ReasonML for me was the ability to have a stack of a single language that reached all the way up to the client browser and all the way down to the multi-process data retrieval and processing system.
What the current status quo looks like, from an outsider, is that ReasonML is used well by people familiar with the Ocaml ecosystem to supplement and write Ocaml through a different syntax. It's also used by people familiar with Javascript to write Javascript to supplement and write Javascript through a different syntax. It works well in each of these domains, and is able to use the ecosystem of tools in each domains, but where they meet gets a little fuzzy, the packages are sparse for both domains, and there's less people already working in that space (and I'm not interested in solving problems because I'm the first in this instance).
This sort of comes to a head around the webserver and/or framework. There are choices in both Javascript and Ocaml. Some of the Ocaml suggestions seemed like a good bet because I could compile the server side to mostly binaries, but how well tested are they? How well built out with features? The ones I saw seemed a bit more bare bones than I was hoping, which might be expected from an ecosystem that has been mostly compiled and focused on application and systems code. So, do I choose Node and some Javascript routing framework and helper libraries? How does that complicate any code sharing I want to do, if there's different HTTP subsystems depending on whether I'm using a JS back-end or Ocaml libraries?
I also looked at doing more JS for the back-end, and just using Node to run everything. Unfortunately, Javascript libraries targeted at the system leave a lot to be desired in my opinion, and I wasn't finding much in the way of a good ORM or query builder that I thought sufficient, and a good database abstraction layer. Again, I'm finding this middle-ground to be weakly supported.
I'm seeing now from replies that there are Ocaml to JS transpilers that would possibly solve some of these problems for me by pushing more of the Ocaml stack to Javascript, but I'm also not sure how I feel about committing to what might be a fragile or error prone process, or how well that interacts with other parts of the system.
In summary, it really felt like there are two strong sub-communities in ReasonML, the Ocaml systems people and the Javascript/React/Web people. They both have a growing ecosystem of packages around them, but they don't necessarily play as well with each other as they could, and getting to that point will allow ReasonML to fulfill a lot of its promise.
Edit: As an aside, from a sysadmin perspective, it's somewhat troubling that the bucklescript toolchain fails to install through NPM as root. Leaving aside whether it's a good idea to have it installed as root (or for that matter whether NPM should ever be run as root), it bothers me that it would fail in some unknown way on a recent RHEL/CentOS system. Failing specifically because it doesn't want to be installed as root with a notice saying as much would have been acceptable, but having a bug like that exist makes me feel like there's not enough people using the system yet to shake out the bugs. It did damage my confidence in the toolchain somewhat.
> Being able to use Ocaml [...] packages is a feature
It's not, it's a necessity. OCaml and Reason are the same language; it's not like Erlang vs. Elixir which both run on BEAM.
The implication I get from that is that pure ReasonML to JS without Ocaml packages is not possible, which is why I asked.
There of course may be some difference on what people consider an Ocaml package, but I would consider anything ReasonML comes with as available in the core as not an Ocaml package, even if that's what provides the support underneath it all, as it's now part of the ReasonML spec (and if the Ocaml package was deprecated, they would find some other way to provide the same underlying functionality so the API did not change).
In any case, that statement about it being a necessity confused me.
His comment may sound like a bit of an non-sequitur, but really the confusion comes from: it's not a delineation between OCaml and Reason, but native packages vs. packages that are designed with JS interop in mind.
Some native packages can be compiled to JS (e.g. if they don't rely on C FFI), but many interesting ones cannot. js_of_ocaml's version of this story is much better than bucklescript's, but honestly I haven't explored it that much in either one.
So what you're saying - write pure OCaml/Reason code, with some JS interop, without relying on native OCaml/Reason packages - is certainly possible! And is probably the most common way of writing apps using the ReasonML syntax today.
I'm reminded of GWT where there is client-only Java, server-only Java, and portable (shared) Java. It's all Java but you definitely have to keep your desired platform's constraints in mind when looking for a library to reuse.
I believe you can get close in ReasonML by making a clear distinction between client (JS) and server (native), and then maintaining a common module of business logic and utilities that you would want to share between the two. This works well up to the point where you want to server-render some HTML, at which point you become sad because you'd love to use the nice ReasonReact component abstraction, but it is not at all portable to native.
Jared Forsythe has been doing a lot of work on making portable applications and libraries in ReasonML, and I do believe that the native compilation story will receive enough love that more people start taking it more seriously - right now the majority of time + energy of the ReasonML team seems to be focusing on honing JS application development, so most of the community is orbiting that use case.
That was the conclusion I came to as well, but I decided it wasn't worth doing so at this stage. I think eventually the ReasonML ecosystem will grow enough to make this much easier. It's nice that you can use Ocaml libs and Js libs in instances, but I think there are use cases where people mistake can for sufficient, when in those cases it's not.
Ideally this would be solved by pushing Ocaml libs towards JS, but I'm not sure how much attention Ocaml has gotten with respect to we stacks (even the lower end of web stacks). It's unclear to me whether high quality libs exist to push towards JS to all the needs that exist in that space.
The target audience for Reason (at least for the moment) are web application developers who want to level up as programmers and want to build elegant, sturdy production systems.
Reason and Elm together are democratizing statically typed functional programming and I believe that the next 10 years are going to see a massive change in the programming language landscape, with the practice of typed FP finally finding adoption in mainstream commercial programming.
The allure of Reason is in its packaging of OCaml to the web browser (which is made possible by BuckleScript - the actual OCaml to JS compiler doing the heavy lifting), its syntax that is familiar to the vast majority of programmers today, and the large ecosystem of bindings, libraries, and documentation that the team has put out.
OCaml is a very pragmatic language; its main pull is its powerful static type system and immutable, functional programming, but the "O" in OCaml stands for "Objective" - it is an implementation of CAML with support for object-oriented programming. But you almost never need objects. OCaml's module system and its idioms allows you to write well-encapsulated code without mutation, and when you actually need to write honest to metal imperative code, you can do it with abandon because OCaml is not a "pure" functional language like Haskell.
The term static type system might put off programmers whose only exposure to types were with Java, C++, C# and similar languages. But this thing is very different. Types in OCaml are more like structs in C; you just say the shape of your data, and you write pure functions that operate on them, and that's pretty much it. The magic happens in how you define the shape of your data. "Making invalid states impossible" is the siren call for statically typed FP languages, and from that flows a lot of natural constraints that can teach us how to structure programs.
The following links illustrate the promise of Typed FP very nicely, do watch it so that you'll have some inkling of the fun you'll have when you start learning ReasonML:
Designing with Types: Making illegal states unrepresentable by Scott Wlaschin. https://fsharpforfunandprofit.com/posts/designing-with-types...
"Making Impossible States Impossible" by Richard Feldman. https://www.youtube.com/watch?v=IcgmSRJHu_8
Ideology by Gary Bernhardt. https://www.destroyallsoftware.com/talks/ideology
Effective ML Revisited by Yaron Minsky. https://blog.janestreet.com/effective-ml-revisited/
Programming Languages, Part A by Dan Grossman. https://www.coursera.org/learn/programming-languages
A lot of the JVM languages allow for this. Kotlin, Scala, Clojure and Fantom come to mind. They all can compile to JS also, allowing for code reuse between frontend and backend.
Some or all of the other languages you mentioned might as well, but if they do I'm unaware of it.
Typically, you get libraries like `foo-scalajs` and `foo-scala` which are basically cross-compiled from a single codebase which is divided into three major parts: common, JS, and JVM.
With Reason you can do the same thing (in fact people have). On top of which, Reason's functors (higher-order modules) allow you to write for multiple platforms in a very clean and decoupled way. They are a great, great boon for software engineering and folks like e.g. the MirageOS have used them to great advantage to target say the unikernel and Unix environments for their apps.
For my own needs, it still seems unclear which way I should go though. The attractive thing about the tools to me is that you can write code that compiles to native and web, in a more or less MLish way (which I like). But it seems like either route works for that. I could just write OCaml, using the regular native toolchain for native compilation, and either Bucklescript or js_of_ocaml to compile to web. Or, I could use the ReasonML toolchain instead. If I'm not primarily a JS dev, is there a reason to pick one or the other? The syntax differences so far don't jump out at me as exciting enough to be decisive. I guess I could try a small side project in each and see how it goes.
This boiled down to where you like C-like syntax or ML-like syntax. You could use refmt to convert between OCaml and Reason syntax automatically. Once you know the language, you can use both of them without any problems
Reason is also easy to install from OPAM https://reasonml.github.io/docs/en/global-installation.html#...
Remember that BuckleScript is a separate thing which you can get any time with just `npm install --global bs-platform`
That's too bad, because all of the marketing (at least around the time I initially looked into it) was something like "OCaml for humans". The JS bit was mentioned as a footnote, but then when I looked at it, JS was the primary target.
I was half hoping that Reason would fix some problems that keep me (and probably many others from OCaml):
* Fragmented build tooling ecosystem with no outstanding options (ideally the Reason community would build a standard tool that improves on other options)
* Fragmented standard library ecosystem with no outstanding options (ideally the Reason community would standardize on a std lib, but this might be overly optimistic)
* Syntax (obviously this is Reason's target problem, but not the most important issue)
* Parallelism (well, Reason probably would never address this one)
I'll look into this and update the post accordingly. Thank you
PL/8, used by IBM for doing their RISC research.
An Overview of the PL.8 Compiler (1982)
http://rsim.cs.illinois.edu/arch/qual_papers/compilers/ausla...
- Lexing/Parsing (ReasonML) - Preprocessing (PPX) - Backend (BuckleScript/js_of_ocaml)
Other parts of the compiler (not as pluggable?):
- Typechecking (which is really what makes it OCaml; TyPpx allows some changes) - Semantics (can be overridden with PPX somewhat) - Optimizations/middle passes (things like Flambda)
I was having a really hard time doing `npm install -g bs-platform`. I kept getting "linking" errors that I didn't understand, since I'm new to Ocaml. It turned out that I needed to get rid of my ~/.opam directory. I did `mv ~/.opam .opambk`, then `npm install -g bs-platform` suddenly worked!
Sweet! Looking forward to playing with ReasonML/OCaml!
So far OCaml seems like a bit of a smoother experience.
That's the only thing holding me back right now.
As far as I know OCaml and JS have stuff to handle this (lwt, promises, event-loop etc.) but the usage in the "front-end" isn't so nice ATM.
Other than that bit Reason React looks very promising. My usual use case tends to start with arbitrary JSON source I'd like to build a tool for. Due to strict typing this runs headlong into hurdles that have ended my explorations...
OCaml's string type is more akin to an array of chars in C, in that it just represents a series of bytes. There's nothing about the string type that describes the contents of the bytes. So while you could put ascii in there, when you to print the contents, it's up to the terminal emulator to decide how to interpret those bytes.
One thing which you can do is include Unicode characters and save them in an OCaml source file. The string type will capture the byte representation just fine.
If you want to enrich the string type to also include format information, you might have to look for other libraries.
And, one last point about the string type: traditionally the string type allowed for mutation. In the last few years this behavior has been deprecated, and in the latest version of Ocaml (4.06), a compiler flag -safe-string has been enabled by default. This makes string immutable, and the traditional behavior has been moved to a new type called bytes.
If you're targeting JavaScript with Reason/BuckleScript, e.g. making a ReasonReact app, you will automatically get Unicode support for strings if you use the {j|Unicode string literal|j} syntax (specially provided by BuckleScript).
[1] http://caml.inria.fr/pub/docs/manual-ocaml/libref/Graphics.h...
```
let message = "hello";
print_endline message; /* Prints "hello" */
```
output
$ bsb -make-world -w
>>>> Start compiling
Rebuilding since just get started
ninja: Entering directory `lib/bs'
[1/2] Building src/demo.mlast
FAILED: src/demo.mlast
/usr/local/lib/node_modules/bs-platform/lib/bsc.exe -pp
"/usr/local/lib/node_modules/bs-platform/lib/refmt3.exe --print binary" -w -30-40+6+7+27+32..39+44+45+101 -bs-suffix -nostdlib -I '/Users/max/dev_projects/my-first-app/node_modules/bs-platform/lib/ocaml' -no-alias-deps -color always -c -o src/demo.mlast -bs-syntax-only -bs-binary-ast -impl /Users/max/dev_projects/my-first-app/src/demo.re
File "/Users/max/dev_projects/my-first-app/src/demo.re", line 2, characters 15-22:
Error: 2122: syntax error, consider adding a `;' before
File "/Users/max/dev_projects/my-first-app/src/demo.re", line 1, characters 0-0:
Error: Error while running external preprocessor
Command line: /usr/local/lib/node_modules/bs-platform/lib/refmt3.exe --print binary '/Users/max/dev_projects/my-first-app/src/demo.re' > /var/folders/7_/ycch7zrn72b3r_f7sw8lp4v80000gn/T/ocamlpp5f8d7e
---
this is straight from the docs. print_line(message) works fine.
https://reasonml.github.io/docs/en/let-binding.html
lib/js was here
https://reasonml.github.io/docs/en/quickstart-javascript.htm...
but someone got that one too