I’ve used TypeScript, Flow, Elm, PureScript and dabbled with Reason/BuckleScript.
I must say the tooling for TypeScript is really nice. It just seem to fly through my files and it’s types.
Maybe the best I’ve experienced currently…
That said both TypeScript and Flow apply gradual typing and that’s quite different from the other (ML style) mentioned languages. In my experience with gradual typing your handholding the compiler a lot instead of the other way around.
And then there’s of course syntax, ADT’s, Pattern Matching, Auto Currying, Immutable Data Structures etc…
Precisely, I really am not a fan of javascripts syntax, plus I know ML (though been years since I’ve done anything sizable in it). And although Haskell’s Type system is stronger than OCaml’s (is there any language with a better type system than Haskell’s?), OCaml makes up for it with two features, the first is that Modules are first class values, I.E. you can construct Modules inline, pass them around, type check on them, type things ‘based’ on them, it allows you to do wonderfully generic programming that is a bit more wordy than the equivalent Haskell, but it also adds more features over Haskell, and like Haskell still puts it leagues over Typescripts abilities. And second is that OCaml is pragmatic, like Erlang, it will sacrifice purity to “Get The Job Done”, but still in a safe way, this is something that Haskell (and it seems purescript too) will not do.
For the little Javascript I do (usually scaffolding and binding to the phoenix library and such) I do use Flow, not as extreme as Typescript and still lets me copy/paste code I find online in (I’m lazy, but I will typecheck it with flow).
Er, I mean, yes, OCaml is so strongly typed, you do not have to even type things but the OCaml compiler is not only one of the fastest optimization compilers out (due to the way the language is structured), but it will fold over your whole program to ensure proper types without you actually needing to type anything (though I often type a lot to enforce more constraints). An example:
type some_type =
{ a : int
; b : string
}
let im_untyped = { a=42; b="Hello World"}
And this compiles fine and well. Now in most languages you’d think that im_untyped just returns a record with an a : int and a b : string, but it is not, let me demonstrate by taking out the b part:
type some_type =
{ a : int
; b : string
}
let im_untyped = { a=42 }
Compiler result:
Line 6, 17: Error: Some record fields are undefined: b
You cannot use a record without defining its type, and since I did not define one in-line on the function it used the record type most recently in scope that most closely matches what I typed, and since there was no other record type in scope other than my some_type record then it ended up changing im_untyped into this for me:
let im_untyped : some_type = { a=42 }
And thus yelling at my that b is missing. If I, however, do this:
type another_type =
{ a : int
}
type some_type =
{ a : int
; b : string
}
let im_untyped = { a=42 }
Then it found a closer match to what I typed via another_type, so it made im_untyped that instead. In most cases I actually ‘do’ type everything ‘public’ from a module, but I rarely do it inline as I did above (though I do sometimes… >.>), but rather in OCaml if the above was in file my_module.ml I could make a file named my_module.mli, which is where I define the public interface for a module (there is a way to define the public part inline in the ml file too, but ick), and I can type the public interface there as well (mli files are actually far more capable than that too) without polluting the actual working code with types everywhere.
OCaml is a wonderfully pragmatic language, it has some odd syntax (no more odd than Erlang, which I also know very well), but they all have a purpose. I’m actually not so much a fan of Facebook’s Reason OCaml variant (yet?), some of its design choices do seem sensible sure, but others are… eeeh, I need to use it to really get a feel for it to decide, but I might like it. Bucklescript is a compiler backend though, and Reason is an OCaml plugin, so Reason works fine with Bucklescript (see an old bucklescript demo with reason here).
(I really hate Reason nonsensically changing the function curry operator from -> to =>, that is probably the bit that bugs me the most and I am not quite sure why…)
Now on to Elm, which I’ve done a lot of lately, I was hoping it would bridge the gap between Javascript and something strongly typed with a functional syntax for me as purescript was significantly too young at the time for that yet, but it failed pretty hard. Its type systems is in-expressive, missing so many ways to make, well, anything generic, no HKT’s, no first-class modules, no ability to simplify code, and all of that I could decently work around enough if not for the community. The community is not as ‘happy’ as, say, the Elixir or Boost or Rust communities that I’ve been in. If anyone asks, as is popular on the mailing list repeatedly, how to generically write a view that has some internal state, say a Ripple button (Google Material Design). In a Ripple Button you have a button, that needs to hold state about its ripple (if its visible, how far along it is, if it should vanish now, etc…), but otherwise the button needs to hold no public state, just assign messages to it to handle click events and such. You may need hundreds of these on a webpage scattered all over the place. The response you get is always, always, either Why are you making such a superfluous visual element? or Scatter its state all over your model that your view can call. The elm-mdl library came out with a great example of how to handle such private states, and it involves just adding a single call to each of your callbacks (still a wart, but only 3 lines of warts), and this design is hated by a few vocal Elm mailing list people (especially one who is with the Elm core, though not Evan himself, he stays quiet) and they constantly belittle both the style and the people, and instead of talking about the problem trying to be solved it just keeps going back to Why are you making such a superfluous visual element? whether it is a Ripple Button, an Accordion, or anything else that has an internal ‘visual-only’ state that may be instanced dozens of times on a page.
Weeeelll’p, I made a brunch plugin for Bucklescript. Bit of a hack since I do not know how to use stdin/stdout with bsc (might be fixed soon), but it works and is glorious.
Given a brunch plugin config of:
plugins: {
bucklescriptBrunch: {
// Commented out means use the bucklescript compiler on the global path, soon
// I will have it default to the one in node_modules once the next version that adds
// Windows support is released.
// binPath: "",
// This is the base directory of your own ocaml files
bscCwd: "src",
// Where to put the temporary build files, this should not be a placed that is watched
// by Brunch or it can grab some javascript that it should not, it defaults to this:
// tempOutputFolder: "tmp",
// Additional parameters, to the compiler if wanted
bscParameters: [
"-bs-cross-module-opt"
]
}
},
modules: {
autoRequire: {
'app.js': ['src/main_entry.ml'],
},
},
And given the file src/fib.ml:
let fib n =
let rec aux n a b =
if n = 0 then a
else
aux (n - 1) b (a+b)
in aux n 1 1
And given the file src/main_entry.ml:
let res =
for i = 0 to 10 do
Js.log (Fib.fib i)
done
It generates into app.js pre-minification of:
// snip brunch header javascript for the javascript module system
require.register("src/fib.ml", function(exports, require, module) {
// Generated by BUCKLESCRIPT VERSION 1.0.2 , PLEASE EDIT WITH CARE
'use strict';
function fib(n) {
var _n = n;
var _a = 1;
var _b = 1;
while(true) {
var b = _b;
var a = _a;
var n$1 = _n;
if (n$1) {
_b = a + b | 0;
_a = b;
_n = n$1 - 1 | 0;
continue ;
}
else {
return a;
}
};
}
exports.fib = fib;
/* No side effect */
});
;require.register("src/main_entry.ml", function(exports, require, module) {
// Generated by BUCKLESCRIPT VERSION 1.0.2 , PLEASE EDIT WITH CARE
'use strict';
var Fib = require("./fib");
for(var i = 0; i <= 10; ++i){
console.log(Fib.fib(i));
}
var res = /* () */0;
exports.res = res;
/* res Not a pure module */
});
;require.register("___globals___", function(exports, require, module) {
});})();require('___globals___');
require('src/main_entry.ml');
//# sourceMappingURL=app.js.map
And for those of you that use elm a bit like I do, well this currently works (compiles and runs, although I’m still building the virtualdom system, inside ocaml itself, currently it re-renders everything but I’m working on the patches now, so much easier to do than in javascript):
type model =
{ count : int
}
type msg =
| Increment
| Decrement
| Reset
| Set of int
let init () = { count = 0 }
let update model = function
| Increment -> { model with count = model.count + 1 }, Cmd.none
| Decrement -> { model with count = model.count - 1 }, Cmd.none
| Reset -> { model with count = 0 }, Cmd.none
| Set v -> { model with count = v }, Cmd.none
open Vdom
let view_button title msg =
node "button"
[ on "click" (fun ev -> msg)
]
[ text title
]
let view model =
node "div"
[]
[ node "span"
[ style "text-weight" "bold" ]
[ text (string_of_int model.count) ]
; view_button "Increment" Increment
; view_button "Decrement" Decrement
; if model.count <> 0 then view_button "Reset" Reset else noVdom
; view_button "Set to 42" (Set 42)
]
And to finish up for tonight, it works TEA’ishly, although the vdom does full re-renders currently, patching is partially made, but the above code makes this:
I always saw Elm as the minimum possible ML you can give JS/Ruby devs and have them understand it within a day. The language is made for and focused on beginners and new users. In fact, Evan recently said he’s currently working on only making the language easier, not any features. I think it’s a good choice but if you’re a seasoned MLer you might as well stick with BuckleScript or PureScript.
Even then, Elm is a subset of ocaml, if someone know elm then they know ocaml well enough to do the same kind of coding. Not everyone needs HPT’s and higher typed objects and such after all. ^.^
However, when you need those features, you really need those features, otherwise the (elm) codebase grows into a huge mess due to the lack of them. Elm needs either HKT’s or HPT’s, and without either it will only encourage simple codebases and a lack of reusability. In addition its compiling times in my project are huge (63 seconds on my last compile), compared to ocaml that is significantly smaller, the language is made to be fast to compile (hence why it has some odd oddities at times, but they make sense once you know ‘why’ they are that way), which is also fantastic for coding. I’ve been programming via bucklescript on my test page with brunch watch --server going and as fast as I hit save the page in my IDE the browser is already refreshed with the new content. Plus I made it fit into brunch’s module system (ocaml module == javascript module, 1-to-1) so you can install the brunch package that lets it hot-code swap individual modules if you want for even more speed. Elm compiles its entire code into a singular mass, which still does not explain how slow it compiles compared to OCaml code that is even larger and significantly faster, in addition Elm’s output javascript is *hard*as*hell* for the closure compiler to optimize out due to how many in-directions everything goes through, thus keeping the code huge, every elm module is included, almost nothing can be pruned. Bucklescript not only compiles an OCaml module to a javascript module (making it easy for the closure compiler to kill stuff) but it also has its own dead-code-elimination, inlining (a lot of modules become unused just because they get inlined to the call sites elsewhere), and everything else it has. In addition the output is easily readable and usable by javascript code elsewhere. I currently see no advantages of Elm-the-language over Bucklescript now, TEA is the big thing Elm has currently, which I’m in the process of porting over (considering doing a very direct conversion first with addons later). ^.^
But really, everything elm teaches you, you can use almost verbatim in OCaml, the syntax is ‘almost’ identical, so if JS/Ruby devs can learn elm in a day then they could learn this too, easily can ignore the more advanced parts, however the advanced parts are there for when they are eventually needed.
I also find it interesting to compare the error messages between Elm and OCaml, Elm’s error messages are designed to be ‘easy’, but OCaml’s is consistent and informative and I also say easy (maybe not as colorful though, but it tells you what is wrong, how it is wrong, and implies how to fix it), it is a much older compiler after all.
You don’t have to tell me about the Elm compiler… it could use some work in terms of bundle sizing, splitting, and quality of generated JavaScript. BuckleScript’s JavaScript is great and it and PureScript don’t ship with a runtime and have dead code elimination so the sizes are small!
I’m looking forward to learning OCaml one day—busy with Elixir at the moment.
Oh the language is fantastic, and even though old it is still developed, has had some wonderful improvements since I’ve last touched it, polymorphic union types are especially interesting, they are like atoms in elixir/erlang, but they can optionally hold typed data, while being type safe.
Got far enough that messages are processing through, buttons click, model works, etc… Plan to work on it a lot this weekend, probably mostly this evening and sunday.
And got VirtualDOM Diffing working, just need to finish filling out the last properties and such, but it works as-is.
Given this code (The Elm Counter example:
open Tea.App
type msg =
| Increment
| Decrement
| Reset
| Set of int
let update model = function
| Increment -> model + 1
| Decrement -> model - 1
| Reset -> 0
| Set v -> v
open Vdom
let view_button title msg =
node "button"
[ on "click" (fun ev -> msg)
]
[ text title
]
(* I have not written the equivalent to the 'Html' Elm library yet, but that is just simple wrappers around this node stuff anyway *)
let view model =
node "div"
[]
[ node "span"
[ style "text-weight" "bold" ]
[ text (string_of_int model) ]
; view_button "Increment" Increment
; view_button "Decrement" Decrement
; if model <> 0 then view_button "Reset" Reset else noNode
; view_button "Set to 42" (Set 42)
]
let main =
beginnerProgram {
model = 4;
update;
view;
}
(* This next line could be done by javascript or whatever, but unlike in Elm you can actually bootstrap in OCaml without ever needing to touch javascript *)
let m = main (Web.Document.getElementById "content")
The above will be the newer versions as I make changes as well (‘mostly’ in sync with the github source, maybe older or newer at times).
EDIT0: I uploaded the *.js.map files too so you can see how I laid out my source tree as well and see what files made what (very in-testing experimenting, not proper setup). I need to figure out how to have brunch use the source ocaml as the map instead of the javascript…
EDIT1: Added requestAnimationFrame support, so it only updates the DOM when necessary now.
Oh hey, the post was moved to ‘Front-End Development’, was just about to move it to that new section.
Either way, vdom working more, made the todo application modeled after elm’s benchmark, and added it to the benchmark.
Interestingly the stock elm benchmark at elm’s blog is showing me that elm optimized is slower than elm non-optimized, which is funky, and happening in Chrome. Edge on the other hand shows a significant difference where optimized is a lot faster. Others in the Elm chat saw the same oddity. An example, here is what I see on Chrome:
Which still seems weird, elm’s optimized version, which cuts out doing work via its lazy call, thus doing less, is slower than its normal, and mine is just barely slower than Elm’s. The only difference between my stock one and my ‘optimized’ one is my optimized one uses keyed nodes, but not lazy yet (I’ve not added lazy yet as I’m trying to optimize well before I add such a shortcut). I’m curious how mine will run once lazy is added to it too. Either way, mine in Edge now:
I have to say that I’m quite impressed by MS-Edge’s javascript engine, not as fast in the general case as Chromes, but impressive still especially in corner cases that my optimized version apparently takes advantage of somehow.
Feel free to run the benchmarks yourselves (and post your results along with the browser that you are using as I’m curious how it runs elsewhere) at: http://overminddl1.com/bucklescript/benchmark/
Do note that I do not have node installed there so the ember/react/angular1/2 tests will not work, only elms and mine as for some reason ember/react/angular1/2 require node to be installed to run client-side tests… >.>
Oh, and that was, like elm, with frame optimizations turned off (immediate rendering, no requestAnimationFrame, which mine has turned on by default). It was as equal to elm as I could get it.
EDIT0: And added a raf version to the benchmark too, not so much for benchmarking as it is cheaty but for testing to see what good it is doing me.
EDIT1: Went ahead and tested in IE11 as well to see what it all does, definitely a much slower javascript and DOM engine overall:
Went ahead and implemented lazy as it was apparently almost too easy to do, new benchmarks:
I did some testing in the profiler of Google Chrome and noticed that it seems to be throttling messages from being too fast, meaning that I do not think it ‘can’ be any faster on Chrome, both Elm and mine are being throttled so we seem to be capped out. That would explain even Elm optimized being slower than Elm non-optimized since lazy calculations are an extra bit of thing to do even if nothing is being drawn for it, but the timeout of the messages inflates it to be roughly equal. This may explain Firefox too.
Consequently it looks like IE and Edge do not have that throttling issue and so can go as fast as they can. Quite interesting and makes the Elm benchmark to be significantly less accurate than the Elm benchmark post led on as it is testing browser-specific messaging times as well.
EDIT0: And of course using rAF is not very useful on this posting as the message count per-frame is quite limited.
EDIT1: I have to have something broken, that is really too fast on IE/Edge…
EDIT2: Actually I think IE/Edge is broken, it is acting very weird…
EDIT3: Ah hah, it was both, fixed the IE problem and am now updating everything that I should be updating. Updated the above images too so they reflect reality. ^.^
MS-Edge is still freaky-fast though, I wonder why…
Great read & effort OvermindDL1. I’m looking with interest at bucklescript, and at some point looking at writing react using it. Still waiting for it to mature a little and creates a community. I know some folks at Facebook already use it to write react components.
I’ve converted some of the basic elm example applications to bucklescript shown at: http://overminddl1.com/bucklescript/
Code available in the usual place (once I git push the updates). This link should be updated as I work on things with more example test cases. The code was a simple conversion from Elm to OCaml, which mostly involves removing the word alias on elm types and adding let before functions and such.
I’ve made the first post into a wiki for you @OvermindDL1 - Elm and SAM have one, guess it’s only fair that Bucklescript does as well (let me know if you would prefer it is isn’t a wiki)