ReasonML versus BuckleScript

In Elm you have to write encoders/decoders, in BuckleScript/Reason you have to write FFI specs. I guess at least with Reason the community is gradually supplying the essential FFI specs (e.g. bs-webapi-incubator) but if you ever have to use a niche library you better have a firm grasp of JavaScript, the OCaml type system, and the BuckleScript FFI extensions/annotations - writing FFI specs can be a bit of a skill barrier in itself.

But once you get past that, BuckleScript can be almost subversively introduced into any JavaScript web application.

I find it a bit disconcerting how much effort Facebook is pouring into making Reason palatable for JavaScript developers. OCaml’s syntax seems to be consistent in itself and I’m not sure that hiding what it is trying to communicate behind a JS-skin is a good idea. I am also concerned that more advanced OCaml idioms are going to become even less comprehensible in Reason (I don’t know OCaml well enough to have any examples - it’s just something that seems to happen when you start oversimplifying things on one end, complexity starts piling up on the other).

Reason feels like a “sales-job” (rather than having any real technical merit) because they felt that plain BuckleScript would be perceived as “too unfamiliar” up against the “more familiar” looking TypeScript.

6 Likes

Uh, it seems pretty easy?

Say you have a javascript function in module Bloop like:

function blah(int, float) { return "hi"; }

It’s just:

external blah : int -> float -> string = "" [@@bs.module "Bloop"]

Yeah this is my big thing about it. I do not like some of their design choices as they try to make the OCaml syntax more Javascript’y. OCaml’s syntax is very consistent and everything is obvious at any point what it does, unlike Javascript…

Most of OCaml’s ‘Advanced Idioms’ surround it’s Module system and Functions (think the power and syntax is similar to Erlang/Elixir Tuple Calls, which is why I really really do not want Elixir to get rid of tuple calls later, grrr). I doubt that can do ‘too’ much to that at least. ^.^;

This is precisely it. ReasonML is, quite literally, just a source<->source transformer, can take OCaml and output ReasonML or take ReasonML and output OCaml (which it then feeds to the compiler when it compiles), nothing more, nothing less. It knows nothing about the type system or anything else of the sort, it is basic source to source transformation, thus it is not even capable of doing anything special or more advanced and at best it can only equal OCaml and at worst it just degrades and loses capabilities.

Heck, just use Bucklescript then, it’s normal OCaml, you can even mix and match ocaml and reasonml source files if you want (and transform them).

5 Likes

I think Reasonml mentions that one of the project’s goals is to get people more familiar with BuckleScript and OCaml in general. The value is has as a transition tool into more serious functional programming is not small. More people being aware that OCaml is even a thing is always a plus, unikernels are really cool.

3 Likes

Maybe I’m being a dingbat but when I discovered that BuckleScript had gone v2.0.0 I tried it out but then wanted to proceed in the browser without necessarily using React. Setting the bar really low I wanted to do this. So far I ended up with

type cssStyleDeclaration = <
  setProperty : string -> string -> string -> unit [@bs.meth]
> Js.t

type element = <
  style : cssStyleDeclaration
> Js.t

type dom
external getElementById : string -> element option = "" [@@bs.send.pipe: dom] [@@bs.return nullable]

external document : dom = "" [@@bs.val]

let changeColor color =
  let opt_elem =
    document |> getElementById "para"
  in
    match opt_elem with
    | None -> ()
    | Some elem -> elem##style##setProperty "color" color ""

which required lots of research on multiple fronts:

… a rather time consuming learning process (but I guess that is what I get for not knowing OCaml up front).

Imitating bs-webapi looks something like this:

(* demo02.ml *)
module Dom = struct
  module CssStyleDeclaration = struct
    type t_csd = Dom.cssStyleDeclaration

    external setProperty : string -> string -> string -> unit = "" [@@bs.send.pipe: t_csd]
  end

  module Element = struct
    module Impl (T : sig type t end) = struct
      type t_elem = T.t

      external style : t_elem -> Dom.cssStyleDeclaration = "" [@@bs.get]
    end

    type t = Dom.element
    include Impl(struct type nonrec t = t end)
  end

  module Document = struct
    module Impl (T : sig type t end) = struct
      type t_doc = T.t

      external getElementById : string -> Dom.element option = "" [@@bs.send.pipe: t_doc] [@@bs.return null_to_opt]
    end

    type t = Dom.document
    include Impl(struct type nonrec t = t end)
  end

  external document : Dom.document = "document" [@@bs.val]
end

open Dom
open CssStyleDeclaration
open Element
open Document

let set_element_color elem color =
  elem
  |> style
  |> setProperty "color" color ""

let changeColor color =
  let opt_elem =
    document
    |> getElementById "para"
  in
    match opt_elem with
    | None -> ()
    | Some elem -> set_element_color elem color
val changeColor : string -> unit
(* demo02.mli *)

I’d like that to be true but if Reasonml catches on I’m concerned that the visible side-by-side support of the BuckleScript syntax will be dropped and swept under the rug.

4 Likes

Eh, worst case you can just use normal OCaml stuff. Plus bucklescript is not the only javascript backend for OCaml, js_of_ocaml was the original, it just outputs really unreadable code (becomes it transpiles from the low-level generated bytecode to javascript instead of the high level typed ast to javascript like bucklescript does). Bucklescript makes MUCH nicer looking javascript, but jsoo can transcompile more things than bucklescript can (specifically you do not need the source of a library as it transcompiles the bytecode to JS); with Bucklescript you need the source.

Yes I agree, Its hard to move traditional FE developer something more above TypeScript …

1 Like

These articles are already over a year old:
Functional TypeScript
The rise of functional programming & the decline of Angular 2.0

So I think it depends where you look. Possibly an oversimplification:

  • Angular/Google: leviathan (“batteries included”) tools, OO-centric, not fond of “functional” (look at Go lang)
  • React/Facebook: more focused (bitsy?) tools, more (but not totally) functionally oriented (ultimately React revolves around render functions but state management varies)
  • Vue.js: Comes across as a lean, pragmatic reinterpretation of Angular, though component’s focus on the render function (mostly via the template) seems more “React-y”. The reactivity system is largely based on the observer pattern (and was likely banking on the withdrawn Object.observe protocol proposal). So it’s more OO-ish but not dogmatically so and slightly less so with Vuex as it centralizes state.

So any “functional motivation” seems to reside with some of the React crowd. So I guess it doesn’t make much difference that ReasonML seems so tied to React on the frontend.

1 Like

FYI:
Jared Forsyth: A ReasonReact Tutorial was updated to Reason 3 shortly after it’s release Oct. 27 - the tutorial isn’t a bad introduction into ReasonReact.

I also came across TDD a ReasonML function. bs-jest seems to work reasonably well. I like to use unit tests to capture my experiments with the language - in this case without having to worry about React. Unfortunately it’s in the old syntax so it needs some fixing up for Reason 3.

bsconfig.json:

{
  "name": "kata-map",
  "version": "0.1.0",
  "refmt": 3,
  "sources": [
    { "dir": "src"},
    { "dir": "__tests__",
      "type": "dev" }
  ],
  "bs-dev-dependencies" : [
    "bs-jest"
  ]
}

package.json:

{
  "name": "kata-map",
  "version": "0.1.0",
  "scripts": {
    "clean": "bsb -clean-world",
    "build": "bsb -make-world",
    "test": "npm run build && jest",
    "watch": "bsb -make-world -w"
  },
  "keywords": [
    "BuckleScript"
  ],
  "license": "MIT",
  "devDependencies": {
    "bs-jest": "^0.2.0"
  }
}

Note: bs-platform 2.1.0 was installed globally (and $ npm link bs-platform)

/* file: __tests__/Listy_test.re
   https://jaketrent.com/post/tdd-reasonml-function/
 */
open Jest;

let _ =
  describe("map", () => {
    open Expect;

    test("map []", () => {
      let noop = Js.Obj.empty;
      expect(Listy.map(noop, [])) |> toEqual([])
    });

    test("map square", () => {
      let square = x => x * x;
      expect(Listy.map(square, [1, 2, 3])) |> toEqual([1, 4, 9])
    });

    test("map String.toUpperCase", () =>
      expect(Listy.map(Js.String.toUpperCase, ["hello","reason"])) |> toEqual(["HELLO","REASON"])
    );

    test("map String.length", () =>
      expect(Listy.map(Js.String.length, ["hello","reason"])) |> toEqual([5, 6])
    );

  });

let _ = describe("trampoline", () => {
  open Expect;
  open! Expect.Operators;

  let trampoline = Listy.trampoline;
  let tDone = (x) => Listy.Done(x);
  let tCall = (thunk) => Listy.Call(thunk);

  let rec even = (n) =>
    switch n {
    | 0 => tDone(true)
    | _ => tCall(() => odd(n - 1))
    }
  and odd = (n) =>
    switch n {
    | 0 => tDone(false)
    | _ => tCall(() => even(n - 1))
    };

  let trampolineFn = (fn) =>
    ((n, _)) => (n, trampoline(fn(n)));

  let mapActual = (fn, expected) =>
    List.map(trampolineFn(fn), expected);

  test("trampolining even", () => {
    let expected = [(0,true), (1,false), (2,true), (3,false)];
    expect(mapActual(even, expected)) == expected;
  });

  test("trampolining odd", () => {
    let expected = [(0,false), (1,true), (2,false), (3,true)];
    expect(mapActual(odd, expected)) == expected;
  });

});
/* file: src/Listy.re */

/* https://gist.github.com/fogus/1224507 */
type bounce('a) =
  | Done('a)
  | Call(unit => bounce('a));

/* Generated JS uses while loop instead of stack */
let rec trampoline = (b) =>
  switch b {
  | Done(x) => x
  | Call(thunk) => trampoline(thunk())
  };

/* Alternately explicitly use a loop

let bounceIt = (start) => {
  let nextBounce = ref(true)
  and result = ref(start);
  while (nextBounce^) {
    switch result^ {
    | Call(thunk) => result := thunk()
    | Done(_) => nextBounce := false
    };
  };
  result^;
};

let rec trampoline = (b) => {
  let result = bounceIt(b);
  switch result {
  | Done(x) => x                       /* Result */
  | Call(thunk) => trampoline(thunk()) /* Never gonna happen */
  };
};
*/

let rec reverseT = ((xs, zs)) =>
  switch xs {
  | [] => Done(zs)
  | [y, ...ys] => Call(() => reverseT((ys, [y, ...zs])))
  };

let rec mapReverseT = ((fn, xs, zs)) =>
  switch xs {
  | [] => Done(zs)
  | [y, ...ys] => Call(() => mapReverseT((fn, ys, [fn(y), ...zs])))
  };

/* (1) Generated JS uses stack for recursion
let rec map = (fn, lst) =>
  switch lst {
  | [] => []
  | [x, ...xs] => [fn(x), ...map(fn, xs)]
  };

   (2) Generated JS uses while loops instead of stack
   ...

   (3) Use explicit trampoline
let map = (fn, lst) =>
  switch lst {
  | [] => []
  | lst =>
    let zs = trampoline(mapReverseT((fn, lst, [])));
    trampoline(reverseT((zs, [])))
  };

 */

/* (2) Generated JS uses while loops instead of stack */
let rec reverseR = (xs, zs) =>
  switch xs {
  | [] => zs
  | [y, ...ys] => reverseR(ys, [y, ...zs])
  };

let rec mapR = (fn, xs, zs) =>
  switch xs {
  | [] => reverseR(zs,[])
  | [y, ...ys] => mapR(fn, ys, [fn(y), ...zs])
  };

let map = (fn, xs) =>
  switch xs {
  | [] => []
  | [y, ...ys] => mapR(fn, ys, [fn(y)])
  };
1 Like

FYI:
2ality: What is ReasonML?
2ality: Getting started with ReasonML
2ality: What is planned for ReasonML?
ReasonML posts at 2ality

by Dr. Axel Rauschmayer of Exploring JS: JavaScript books for programmers

I was also concerned with this at first, mostly because I prefer ocaml syntax over js syntax. It’s just a preference, but I know there are plenty of developers out there that have to have something “familiar” before they will even look at it, so I understand Facebook’s reasoning behind the decision.

I came to terms with it by simply realizing that I could just use ocaml even with react (in desurgared form: https://github.com/cxa/ppx_bsx).

The positive thing about all of this is that the ocaml semantics are still there. Getting developers to use them and thinking functionally is more important than how they are presented (syntax).

1 Like

ReasonML seems like tilting at windmills. Javascript developers are not going to flock to it, its way too different and demanding. Typescript and Flow make more sense to Javascript developers but most don’t even want that. Functional programmers would rather use OCaml syntax. So whose problem is it really solving?

2 Likes

Facebook’s. It’s nice that it’s getting press, but it exists to better manage the complexity of FB frontend.

I really like the language; I don’t have any preference re syntax, but I understand the decisions made for Reason, and they aren’t arbitrary at all. IMO the tooling is already more user-friendly and will become more so. It works, really well. I currently use JS almost exclusively for front-end. I have no issue whatsoever with ML syntax, but it’s really nice switching from JS, hardly any context switching, which I guess is a huge thing from FB’s PoV.

Something has been driving the development of

  • ClojureScript, Om, Om Next, Reagent (Re-frame)
  • PureScript, Thermite, Halogen
  • Elm

Bloomberg’s reason:

BuckleScript is mainly designed to solve the problems of large scale JavaScript programming.

When phrased that way it sounds like they had Node JS in mind but then why not just use OCaml straight, like Jane Street Capital? The only other option would be some “large scale” browser based UI.

But it seems that Bloomberg is perfectly happy for Facebook to take the wheel in the browser. To a certain degree it makes sense given that most of the “Elm-alternatives” wrap React.

I believe that Reason has reached the point where it is on the verge of stunting the growth of Elm’s community. Elm still has the better beginners story and documentation to get anybody started. But it wouldn’t surprise me if Building a Graphical IDE in Elm/Purescript isn’t an exception, that certain ambitious UI projects may not want to accept the inherent design limitations of Elm. And “with Elm I won’t have to use JavaScript” never really works; for interop you better know your JavaScript.

Reason never made such promises‡. The React documentation is pretty clear that JavaScript competency is expected and for ReasonReact:

The documentation assumes relative familiarity with ReactJS.

Prior to Angular 2 TypeScript’s main use was to provide type definitions for IDE autocompletion but Angular has become a significant driver in it’s adoption. In many cases “gradual typing” is going to be a waste of time because most of the “tricky typing” will never be specified (see also Types are like the Weather, Type Systems are like Weathermen - Matthias Felleisen). The good thing with OCaml/Reason is that any module is 100% type-checked (provided the FFI is accurate) - so gradually converting a codebase module by module is much more likely to be successful than using TypeScript or Flow.

Unfortunately TypeScript and Flow are easier to adopt, so lots of time will be wasted there.

‡ To be fair Elm never made such promises either - but Elm as a “viable (total) JavaScript avoidance strategy” seemed to be a common expectation among newcomers.

2 Likes

You are explaining what BuckleScript brings to the table. I’m talking specifically about ReasonML syntax. Why do we need that? People who actually care about correctness and are motivated to pursue it aren’t going to be turned off by OCaml syntax. Everyone else should just use TypeScript. If you use it with all the strictness flags, disallow in-explicit any etc, TypeScript is a pretty powerful type system that can catch most issues. I can’t think offhand of a type system feature OCaml has that TypeScript lacks. I guess unions are not exactly the same thing as ADTs, and it doesn’t have pattern matching, but strictly talking about type system its pretty well-fleshed out. If you really want more in a type system then in my opinion you want PureScript because OCaml and Elm are both lacking typeclasses and higher-kinded types.

You’re preaching to the choir - I personally don’t think it’s needed at all. But I also think that it’s a “if you can’t beat 'em, join 'em” situation. From what I’ve seen there is very little momentum behind “vanilla BuckleScript” OSS, possibly because Facebook is purposely grabbing almost all the attention for ReasonML.

At worst ReasonReact is just going to fragment the “functional programming in the frontend” space even further. But given that some companies have a strong preference for corporate backed OSS, that React JS already has momentum (and has been repeatedly the foundation for other FP frameworks) there is a chance that ReasonReact will actually take the lead in the “functional programming in the frontend” space (but even it that does happen that space may not grow beyond “niche” status - and even Google couldn’t get outside traction for Dart).

I can’t think offhand of a type system feature OCaml has that TypeScript lacks.

Comparison by a(n obviously biased) BuckleScript/ReasonML contributor.

Personally I think that immutability by default on a language level is a big plus.

If you really want more in a type system then in my opinion you want PureScript because OCaml and Elm are both lacking typeclasses and higher-kinded types.

PureScript may well be a better choice and has so far been the default escape hatch from Elm. But I suspect that OCaml’s module system offers a lot more than what is possible in Elm. The idea behind ReasonReact is to provide a better React experience than with JS/ES (ReasonML hypes itself as ES2030).

On a separate note I would also expect a competent ReasonML developer to produce higher quality‡ JavaScript code when the situation calls for it - but admittedly that is conjecture/prejudice on my part.
(‡ compared to somebody who can’t be bothered to move beyond just JavaScript/TypeScript.)

So skepticism against ReasonML is entirely justified but at the same I think it is creating some opportunities that warrant observation.

2 Likes

Primarily just to work with things like server side React and such. Bucklescript’s PPX you can use in a normal OCaml install to get Bucklescript’s extra features but in native compiles too, conditional compilation too if you need it that low-level. :slight_smile:

Bucklescript has plans to verify against Typescript types too in the future if they exist. :slight_smile:

Bucklescript can already mostly output typescript types when it outputs javascript too. :slight_smile:

We don’t really, in my opinion, the syntax is longer, more verbose, harder to read, a TON more things like parenthesis that makes traditionally trivial OCaml things very difficult to mentally parse, etc… >.>

I’ve not actually used Typescript yet, if you know Typescript any chance we could spawn off a new thread, I’d be very curious to explore this topic! :slight_smile:

Elm is, but OCaml is not. OCaml is a Module Language (hence the ML in it’s name), and it’s module implementation is the most powerful of all module languages, it is a full Higher Polymorphic Type. HPT’s can emulate both typeclasses and higher kinded types (though ‘slightly’ more verbosely, but that will be fixed in the future when the Implicit Module PR lands), and it does it far more efficiently too.

As a direct comparison, the OCaml way to do typeclass type stuff is via the Witness pattern (usually built on the modules), and though yes you do have to specify the witness in the function signature, you do the same with typeclasses in Haskell too as a big example, so the load there is the same, and specifying the witness at the call site is a single token extra of verbosity compared to Haskell, that will be gone when Implicit Modules lands.

In addition, OCaml is able to inline the ‘typeclass’ calls far more often than Haskell can, while also being able to override the definitions on a call-by-call basis, which is impossible to do with typeclasses.

Overall HPT’s are not just significantly more powerful than Typeclasses and HKT’s, but can represent far more typed patterns than Haskell can even including all of the ‘usual’ extensions and even a lot of unusual extensions, all with a unified syntax, unlike Haskell. OCaml is more powerful than Haskell, it has a more powerful type system, it has an extensible code generation system, it generates code that is faster and smaller than about every non-low-level language out (and it is on par with low-level languages, it averages code generation about 1.5x-2x the speed of C, and if you program well you can easily surpass the speed of good C code).

So no, I would absolutely not choose Purescript over OCaml. Purescript is far more limiting.

2 Likes

You don’t have to specify type classes in Haskell function signatures, they will be inferred based on usage of the class members or other functions which require them. Generally people do specify signatures for top-level functions though and -Wall commands it.

I may have to just take your word regarding HPT because I haven’t used OCaml enough to really see the power of its module system. Its worth mentioning though that Haskell actually has a signature/module system now too now that backpack has landed in GHC (but PureScript doesn’t have this). The author of that blog and one of the backpack collaborators also knows and has written about OCaml and Haskell differences (he’s written the only OCaml for Haskell developers guide that I know of, which is basically the extent of my OCaml knowledge). I’m pretty sure that informed his design decisions in backpack but they had constraints to to stay interoperable.

For some reason, there is a lot of misunderstanding between Haskell and OCaml camps with the OCaml people thinking purity is a real drag and the Haskell people thinking 5.0 +. 6.0 is a real drag. There seems to be less cross-over than I would have expected.

Yet Purescript can generically derive serialization classes, so that it can actually, you know, speak JSON without writing encodings by hand, which is something BuckleScript and Elm both lack. For some reason yojson is still not a thing in Bucklescript. This is an egregious omission in a web-first language. I may give BS another look when its fixed because I love its developer experience (speed == joy).

You don’t need it. But you’re not an enormous company experimenting with OCaml, and your usecase is probably not the same (convert enormous codebases to a safer language & provide a fairly easy transition path for a potentially huge amount of JS developers that work at FB). Doesn’t seem much point in Typescript, as that would involve dumping the OCaml type system that’s already bolted onto all their JS. Also Typescript doesn’t compile to native code, OCaml does.