I didn't think much of this article ("Another biased comparison, yawn") until I got to the Reducer example.
I read up on reducers for Redux and Cycle.js, but avoided using them. The Typescript examples are so cumbersome; I'd rather do something else where my code is more understandable.
But in ReasonML it's elegant. It's awesome! I'd choose to use that.
Sure they do the same thing. But as the developer who has to commit to reading and maintaining this code, I know which I'd rather write and come back to in 18 months' time.
I wonder if people (including me) are too afraid to write a little DSL/compiler to get us out of these spots. I.e. use Typescript most of the time, with a DSL for things like reducers. Another thing where a DSL/compiler step would be good is to convert a type into a function that will validate arbitrary JSON (e.g. received on the wire) conforms to the type.
The reducer is a case where the functional programming concepts like sum types make a dramatic difference, but they are widely useful across your codebase.
It shouldn't be a surprise that React works well in ReasonML - the first prototype of ReactJS was written in StandardML before the creator ported it to JS (https://reasonml.github.io/docs/en/what-and-why.html)
Looks like another biased comparison to me. You could easily rewrite the TypeScript reducer example (which fails to compile btw) to be similarly concise while being as safe.
interface State {
movies: string[]
}
// you may also use { tag: "Action", payload: string }
// if you prefer something more structured
type Action =
| ["AddMovie", string]
| ["RemoveMovie", string]
| ["Reset"]
const defaultState: State = { movies: [] }
const reducer = (state: State, action: Action): State => {
switch (action[0]) {
case "AddMovie": return { movies: [action[1], ...state.movies] }
case "RemoveMovie": return { movies: state.movies.filter(m => m !== action[1]) }
case "Reset": return defaultState
}
}
const someAction: Action = ["AddMovie", "The End of Evangelion"]
TypesScript's type system is actually very flexible and advanced [1] compared to most type systems since it had to adapt to the very "dynamic" structuring of JavaScript in the wild.
ReasonML variants are type constructors. Your example completely eliminated all action creators (constructors). Once you add those functions back, most of the type bloat reappears.
I wouldn't want to work on a project where everyone was creating object literals and passing them to dispatch everywhere.
Yes, I eliminated the action creators. I don't see why they're necessary (maybe there's something about redux I'm missing?). The type constructors of ReasonML cannot be used as functions, so I think my rewrite is fair.
If you insist on having the action creators, they can easily be written as one liners each, which is a lot less bloated than the original example.
What's the issue with passing object literals? I know it looks a bit hacky and messy, but if it's typesafe (which it is) then it doesn't seem like a real issue.
Let's say you need to update your function signature with an additional, optional parameter with a default value. You can go to the definition of the action creator and change it. Since you've defined an interface for the action object, finding reducers that use it will be reliable and easy.
If such a creator doesn't exist, you must grep your codebase for every instance of the object and ensure they deal with the optional bit. Murphy's Law guarantees that someone will have decided to procedurally generate the object making finding every instance very hard. Next, you have that reducer lookup, but you don't have a shared interface, so you're stuck in grep land once again to find all the reducers.
Finally, copy-pasting an object literal all over generally results in a larger bundle than reusing an action creator which is smaller and also minifies better.
If the lack of action creators is a serious concern, then the one-liner-each action creator I suggested should be sufficient without adding much bloat. How would the ReasonML example deal with that change though? Can the variant constructors take in optional parameters?
Using Murphy's Law is not very convincing. Even with action creators Murphy's Law guarantees someone would have just constructed the objects manually anyways. Having to grep the tag is not ideal, but it's not likely to be a problem in practice.
I doubt that the bundle would be significantly larger (especially after compression). Using actions creators introduces additional (function call) overhead too (and that would be insignificant as well).
I'm fairly new to both TypeScript and ReasonML (both within the past month). The author's reducer example closely matches that of the official Redux documentation [0], so I'm not really sure it's biased. But, I could buy that it's not a best practice. Absent anything else, I've been using the patterns demonstrated in the docs. Is there a better resource I should be following?
I don't have experience with Redux so I cannot comment on that, but I do believe my rewrite is equivalent to the ReasonML example (it should express the same thing and still be typesafe).
While I'm sure the author did not intentionally try to make TypeScript look bad, (imo) they didn't put enough effort to make it look good either. There's a lot more to TypeScript that this post fails to mention. ReasonML is a good language (I think OCaml was just fine though), but TypeScript is good too.
The author says "ReasonML is everything TypeScript tries to be (and a bit more) without all that JavaScript weirdness.", but I strongly disagree. Embracing "all that JS weirdness" is the whole point of TypeScript, and what makes it so successful. Unlike most typed languages, TypeScript (for better or for worse) adapts its type system to the developer's code, not the other way around. This is why its type system is much more expressive than many other type systems including ReasonML's.
The question is as much how exhaustive you want your Redux usage typed, and also how much you want to stick to the "action creator" pattern. A lot of the action creator ceremony is to avoid throwing the wrong types of data to Redux dispatch and make it easier to refactor things when the data requirements for an action change. Typescript handles a lot of that no matter what, and you can lean on the Typescript type system more and the "action creator" pattern less, but it won't look "Redux enough" to more JS-focused developers and that may or may not be a source of friction.
Which is a long way around to: the pattern in that recipe is a good one, it's quite exhaustive in making sure everything is typed, and it's quite familiar to almost any Redux dev you will meet whether they come from JS or TS. You can get away with other patterns/recipes if you want looser typing or "looser redux", based on the compromises you are willing to take, but currently there's no strong recommendation for any such alternative pattern because the common recipe is "good enough", even if it has lots of little bits of extra "ceremony". (Ceremony isn't necessarily a bad thing, sometimes those rituals are helpful in getting your thoughts down and making sure you have everything you need planned out.)
But that's also part of why the example in the author's post is unfair to Typescript. It's as strict on types as the Typescript pattern, but it's "looser redux". It sort of has action creators, but those aren't proper functions from a JS perspective because they are type constructors that don't properly exist in JS. They also don't exactly correspond to/produce the usual sort of "plain JS objects" that Redux expects when dispatching ({ type: "DISCRIMINATOR_STRING", … }), which will worsen and even in some cases may break Redux debug tools/experiences.
(Semi-related, there is a Stage 1 proposal for pattern matching in front of TC39 that would also help reduce the switch case that TS needs to pattern matching closer to the ReasonML example. TS sticking to Stage 3+ proposals now means that it won't get some of these sort of nice to have things until JS does.)
Okay, thanks for elaborating. I think the part I had overlooked was the variant constructor not being a function.
I had mentally mapped that as a `dispatch` call with the variant being the payload. But, yeah, it looks like the author either made a mistake or took a shortcut there.
It's great to see more comparisons and articles about ReasonML and this one is really concise! But I do wish articles were more clear about the relationship between ReasonML, OCaml, and Bucklescript.
> It is also interesting to look at what javascript was generated by ReasonML
This is where BuckleScript should probably be mentioned first. TypeScript is both the name for the language and the tsc compiler, since it's a single project. But with BuckleScript that's not the case.
> BuckleScript is the tool that transpiles ReasonML to JavaScript.
There's also the older js_of_ocaml (JSoO) project, and there's more great Reason/OCaml tooling for native, like esy and dune! It's really exciting, but these small details are rather interesting and it's important to highlight them to avoid confusing beginners.
Sadly, BuckleScript tries to do everything. It ships with a not so-great build system, a good compiler to javascript, a standard library replacement and it invites you to use npm for package management.
Dune is the build system which has become the standard in the Ocaml community. It can use the native compiler or js-of-ocaml to produce javascript. It doesn't support BuckleScript however.
Esy is a tool which allows you to manage your project dependencies. It can pick package from both opam and npm and uses Dune as a build system. It's actually very good.
As Facebook mostly pushes ReasonML towards Javascript developers, they mostly advertise BuckleScript. From what I have seen, the Ocaml community mostly does not care about Reason. They had no issue in modifying tools to support it and noone is going out of its way to hinder it but there is no particuliar enthusiasm towards it.
There's no particular enthusiasm towards quite a lot of things in the OCaml community :-) there are even some holdouts who don't like or use dune/opam. I wouldn't take that as a point against ReasonML/BuckleScript, which is bringing devs en masse to OCaml who would have never looked at it before.
This is a very good point and I'm glad my other comment isn't the only one bringing it up.
Frankly, of the time I spend reading documentation for my (Web) project right now it breaks down into 5% reason-urql (thanks, BTW, can't wait for 1.0), 10% reason-react, 15% ReasonML, 70% bucklescript.
Even the ReasonML docs that I am reading are pretty heavy on the bucklescript.
I'm building a project in ReasonML right now with reason-react. The Reason-only parts are actually a joy. The problem is interop with the wider ecosystem, and bucklescript.
They are serious problems. You end up writing a ton of [@bs.... ] annotations to get things to work together that ultimately result in runtime errors.
I hate battling a compiler (with sometimes quite cryptic errors, particularly identifying syntax errors) only to find that I can't actually free myself from the same type of errors I'd see with regular old JavaScript.
I can dig through my discord history and give more specifics. But given the massive learning curve and poor tooling that exists these days it's a step backwards from mainstream production JS development.
That said, the community has really got some wonderful folks out there, and is very helpful. I hope they can figure out interop with basic JS like promises. And tooling. And let me use async/await. And make bundle splitting easier to implement.
In what ways does it fix my major issues with bucklescript (simple interop with JS libraries / object / output, promises, async/await being highest on the list).
I have to say though, the ELM architecture actually lends itself better to OCaml than ELM, because functors give you a really nice way to compose components.
My recommendation for using Reason in production is to go for it -- but only if someone in the team has previously shipped production software in a typed functional programming language. If not, use it in a non-critical project, have a pair of programmers build up expertise over around six months of full-time work, and then make the jump for the entire team.
Regarding bindings -- which external libraries were you using? Component libraries written for React could be a fair amount of work to write bindings for, but I anyway prefer hand-rolling components with Tailwind, so that hasn't been a problem personally.
Bindings were indeed difficult when I started out - Javascript can invoke functions in a myriad ways: new xyz(), object.key, object.key(), module defaults, Math.abs etc. We have to map them to OCaml's semantics (easy), and figure out the BuckleScript macros for them (easy with practice). I recently wrote a bunch of bindings for Adobe XD, and it was surprisingly fun.
I've been using of BuckleScript and OCaml for a couple of years now. I'd been using OCaml for a long time before then. I tried ReasonReact, and found that I preferred the OCaml + vdom library way.
I first used my own minimal bindings to Mithril, but then later moved to an Elm Architecture implementation for bucklescript: bucklescript-tea. After being on a deadline and either completely misunderstanding or finding significant show-stopping bugs in bucklescript-tea's virtual dom implementation, I reimplemented the parts of the API I'd used from bucklescript-tea with a minimal alternative that called out to snabbdom for the vdom stuff. This was mainly as a learning exercise, which took all of about two afternoons of fairly relaxed work.
The only places I have needed to use annotations with this approach is in my modules that call out to JavaScript. This is really unavoidable, and you'd surely find the equivalent information would have to be provided if you used F# or PureScript, or your compiler simply won't know the types.
I'm sure my approach, with layers on layers of functions to build virtual dom nodes, is certainly not going to win any benchmarks, and it's certainly not novel – but it's good enough, and my productivity is up. I can leave a project for months and be relatively unsurprised by what I find when I return – because there's usually only one sensible way to do things. Everyone's different, though, and I fully respect it may not work for all.
The key lesson I've learnt is that interop is painful because the some of the bucklescript types do not neatly map how you would necessarily imagine to javascript types. But if you do this at the edges, and write sufficient wrappers, you won't have any problems.
The biggest pain point for a while has been that bucklescript is based on an old version of OCaml – and there have been some very nice additions to the OCaml standard library since then. That said, I think it's excellent that we've got two OCaml -> JS pathways in the OCaml world now.
Just fyi– bs-platform 6 and above are based on OCaml 4.06. So, still not the latest, but a bit more recent! In particular, it supports record types directly embedded in variant constructors without having to declare them first.
>It is also interesting to look at what javascript was generated by ReasonML
```
function updateName(product, name) {
return [name, product[1]]
}
```
Records in ReasonML are represented as arrays. And generated code looks like a human wrote it if humans could remember indices of every property of every type like a compiler does.
This seems like a massive headache, what about JSON? or accessing dictionaries? Do you need to parse every js datastructure to a ReasonML record?
No. Reason (bucklescript really) maps to the full range of a data structures. So use a dict as a dict, a record as a record, an array as an array...etc. It will map back to what makes the most sense in the JS world.
As opposed to JS, where really all these things are overloaded onto the same data structure ( object ).
But to be clear, yes, interop with regular JavaScript and JSON is a bit of a pain in the butt. Bucklescript has lots of helpers but it's definitely the place where the soundness of the type system has broken down for me in the past.
Does ReasonML infer that a type "Product" is the only one in the visible namespace with a "name" property or does it infer at use time that the formatName(...) invocation is valid because the argument has a "name" property?
They're separate concepts. Maybe I want to ensure that I'm receiving a product as it makes no sense to invoke formatName(...) on other things with names like a Cat or Person.
And the generic "thing with a name" one is possible in TypeScript too:
IIRC if there are several possible choices, the compiler will complain (with a fairly incomprehensible message), and you will have to explicitly provide the type.
They're different concepts with different ways of being expressed in OCaml.
In this case it truly is a Product and if you want to reuse a field name you'll need to enclose that type in a module.
Row polymorphic -- "thing with a name" -- is possible through the object system which is accessed through a different syntax, and works like go interfaces in practice.
Yes. Which is why OCaml (and thus Reason) separates it[0] from the nominative "structures".
> Maybe I want to ensure that I'm receiving a product as it makes no sense to invoke formatName(...) on other things with names like a Cat or Person.
You're really deep into this strawmanning thing.
[0] pretty completely, you can't use nominative types in a structural context, they are not compatible. In fact structural types are sets of methods, something which doesn't exist on the nominative side.
I think the comparison in the redux example really isn't fair. He defines just one enumeration in ReasonML and uses many different constants and interfaces in Typescript.
The equivalent of
type action =
| AddMovie(string)
| RemoveMovie(string)
| Reset
Since TS is just JS, you could just dispatch to arbitrary strings, but the TS type system isn't going to help you out very much. The approach the author is taking is the one described in the Redux documentation [0].
Hi, I'm a Redux maintainer. FWIW, there's apparently a ton of ways to handle typing Redux with TS (and that's even reflected in the discussion for the PR that added that page [0]). A large part of it seems to revolve around whether or not you're trying to apply action types to `dispatch` to limit what actions can be dispatched.
I'm comfortable with TS at a general level (declaring interfaces for API types, reducers, and React component props), but a lot of folks seem to go crazy trying to get 100% type coverage. I personally aim for more of an "80%" sweet spot for typing - trying to trade off overall benefit with not trying to tie myself in knots adding ridiculous type declarations.
I actually just wrote a tutorial page for Redux Starter Kit that shows how to use it with TS and `useDispatch` [1], but had someone point out that one of the examples I was using for thunks was losing a bit of type safety [2]. This stuff is hard :)
Thanks for the feedback. The earliest examples I had see of using redux just passed strings around and struck me as being error-prone. So I was happy to see const fields used as the action types. The TypeScript approach looks reasonable to me, if not verbose. But it looks like there's no real consensus on what the best practice is. Certainly some food for thought. Thanks!
Right but the type script version isn't type checked. You need explicitly check for those strings at runtime. The type checker needs to prove the program type correct without explicit input from you.
ReasonML has better theoretical foundation. The variant type is a primitive type that TS and many other languages miss.
type State = {
movies: string[]
};
type Action =
| {type: 'AddMovie', movie: string}
| {type: 'RemoveMovie', movie: string}
| {type: 'Reset'}
let defaultState:State = { movies: [] }
let reducer = (state: State, action: Action): State => {
switch(action.type) {
case 'AddMovie': return { movies: [action.movie, ...state.movies] }
case 'RemoveMovie': return { movies: state.movies.filter(m => m !== action.movie) }
case 'Reset': return defaultState;
}
}
/* No need for additional functions! */
let someAction = {type: 'AddMovie' as const, movie: "The End of Evangelion"}
reducer(defaultState, someAction);
The secret is that typescript doesn't actually lack variants - it does have the equivalent of polymorphic variants. Try adding a new action type, you will get a type error for the reducer return type. It won't be a pretty type error, but you can't forget to add a switch case.
It is string comparison, although in terms of performance in JS string constant comparison is done by reference so its much faster.
I believe this is closer to polymorphic variants than to actual sum types (global namespace like strings, comparison by name). You can tighten it a bit by switching to symbols instead of strings, which would guarantee unique tags, getting closer to classic variants. However TypeScript isn't big on nominal types, and for a good reason: the node module system for example can easily cause two versions of a module to be instantiated in the same program which in turn would cause two different and incompatible instances of the symbols and symbol types.
Nevertheless I would still say that FP-like patterns are supported reasonably well.
However If it is string comparison on the TS level then that means it isn't type checked. Are you sure this is what you mean? That means I can add a new case for string "asdasfsdfds" and TS won't throw a type error.
The variant type is not exclusively an FP paradigm.
So you can't add a new case because while the value is a JS string, the type is a restricted subset of string i.e. a union of only 3 possible string constants.
Therefore you are right - its not quite a string at the TS level - its a subset of string.
p.s. you can make unions of any primitive values in TypeScript:
let a:3 | "hello" | true = 3;
a = "hello"; // valid
a = true; // valid
a = false; // not valid!
or between any type and other values, really:
let a: number | 'NotNumber' = 0;
a = 1; // ok
a = a - 1; // still ok, assignment narrowed the type to number
a = 'NotNumber' // ok
a = a - 1; // error! narrowed the type to the constant 'NotNumber', can't subtract anymore
a = 'Hi' // ERROR
TS is not always that smart about it (compared to Flow), but sometimes it works out quite well :)
I wish this comparison included more information about the developer experience of TS vs Reason. In my off time I've tried out Reason, but one of the major sticking points was how _poor_ the experience was setting up an editor with autocomplete and type hints. You get that basically out of the box with TS, but Reason & OCaml have a more fragmented ecosystem, so it's much harder to get started and feel productive.
IMO, the beautiful type system is nice, but the DX with TS is much more productive than Reason or OCaml.
PS: Do you think my comment is dumb? Reason/OCaml are easy to set up? It's as easy as 1-2-3? Please link me tutorials or resources that prove me wrong. I'd love to get started, but DX has been the major hangup again and again.
Every technology created at Facebook is meant for internal use. The wider community then goes through the pain of making it usable. Microsoft's mission on the other hand is pleasing developers hence VS Code and TS.
I remember when people got excited about Hack, Flow, and Nuclide (or whatever the Facebook IDE was supposed called), but eventually realized the same things that you and GP realized.
Microsoft has paying customers who are devs, and FB doesn't. That explains a lot.
Ugh yeah, the same thing happened with Flow. Massive immediate adoption by react developers and then FB basically saying "screw you" no roadmap and we're never going to add the features you want. I think I still have an open issue in the flow repo for a roadmap.
Flow engineers communicated a few months back to the community saying that they do care, they just were a small team working on the project and had pressing issues at FB to solve first. They mentioned that Flow was really slow for the large-scale projects that existed at FB and so performance was the immediate concern and that ate a year of time after they open sourced the project.
This really depends on what you care about. Flow is much better at ensuring type safety in your code while TypeScript has some pretty glaring holes in this respect. In particular, TypeScript allows covariant assignment and param passing which makes it really easy to write unsafe code.
The covariant param passing was fixed some time ago with the (now on-by-default) `strictFunctionTypes` option. Eg `function f(cb: (_: Animal) => void) { cb(new Dog()); }` can no longer be used as `function takes_cat(_: Cat) { } f(takes_cat);`
Assignment is still there though (ie you can still assign a `Cat[]` to an `Animal[]` and push a `new Dog()` into it).
Your example is identical to the case I covered in the second paragraph of my comment. ie your code compiles because it is legal to assign a `Cat[]` to an `Animal[]`, as I said.
When you mentioned "covariant param passing" I assumed you were referring to the long-lasting issue about callbacks, hence my original comment mentioning that that issue has been fixed. It's not useful to differentiate between "assigning a value to a variable of a certain type" and "passing a value to a function parameter of a certain type" because those are identical.
I'd bet Flow is basically being replaced by ReasonML at Facebook[1]. Bolting a sound typesystem onto Javascript is really hard, and Flow is making a truly valiant effort, but it seems to have stalled out. Instead, ReasonML is based on starting from a "cleaned up" semantics and type system, then translating it into raw Javascript. This avoids all the weird edge cases and type inference problems one runs into when trying to use Flow[2].
(I don't ever have to have any `any` in my ReasonML code, but it was unavoidable with Flow.)
The way the team communicates with the community is sometimes frustrating. Also, a lot of the decision-making (especially around React/JSX typing) happens behind closed doors, with little to no explanation as to why they were made.
Yeah, FB releases libraries only to avoid walled garden situation when they can't hire new people because they're not pre trained or they don't want to use closed and internal only technologies. Remember React licensing problems?
These days most of frontend jobs are actually React jobs, which i don't like, I like vanilla web.
Over the time I began more and more appreciate Microsoft's "boringness" (which is debatable itself) because their tools are real products, not marketing tools for developers. What matters more for serious project is tools quality, not few hot CS ideas loosely implemented on a knee.
I'd argue Yarn, React, Jest, PyTorch, Hack (and flow even if it's not used much) are examples that show not true. GraphQL may be one of the one of the only examples that needed a bit more to be usable outside of Facebook. All of the others are generally usable out of the box.
Rust was created to solve's Mozilla's problems with C++
Go was created to solve Google's problems with C++ and engineering at scale
The fact that TypeScript wasn't created to solve a big issue internally at Microsoft is exactly what makes me skeptical of it and personally I view it as a trojan horse, just like VSCode. Sure, today it is done for benevolent reasons, maybe, but what about tomorrow? What if that trillion dollar market cap starts tanking? Then The benevolent VSCode and TypeScript ecosystems become levers to pull in the great big machine.
I would almost feel better if I knew it's stated explicit purpose upfront and what Microsoft's long term goals to monetize the ecosystem/platform were, because then I know what I am signing up for.
> The fact that TypeScript wasn't created to solve a big issue internally at Microsoft
AFAIK it was, the issue was the poor quality of Javascript development they were having and their inability to really get good tooling and static analysis. Then it kind of exploded as other people said "That's cool" and they then pushed it out as open source so that if they did need to let it go it wouldn't be totally dead in the water.
Typescript was created to solve big issues internally at Microsoft, to deal with JS at scale. Microsoft has huge JS codebases, almost all of which are Typescript today. Office.com (including in-browser, online versions of Word, Excel, PowerPoint), Outlook.com, OneDrive.com, portal.azure.com, dev.azure.com, and so on. Microsoft may not be as recognizably a "web applications" company, but they do have a number of really big web applications, and they had real scaling problems with JS that Typescript was directly built to help with.
VSCode was started to see if they could build a good web application for development. Portions of it directly power a number of tools of Azure (on portal.azure.com), portions of it also directly powered the Dev Tools inside IE10+ and Edge "Classic". Bundling the web app into an Electron container after it had already paid for its own engineering efforts several times over as reusable sets of components for multiple large web apps Microsoft needed hardly seems like a "trojan horse" to me.
TypeScript was created to solve issues with front end development at MS, its just that they were also genuinely interested about solving other people's problems.
You speak with authority, but with ignorance. MS had a lot of JavaScript and needed to put some semblance to the madness of development. Read early history. Watch some talks of Hejlsberg, they are insightful and makes you a better developer.
> Every technology created at Facebook is meant for internal use.
Technology is a stretched term for ReasonML. It's a simple syntactic sugar for OCaml. It uses standard OCaml compiler and standard JS transpilers, like js_of_ocaml and bucketscript.
and then add the ocaml layer to my spacemacs config. This gives me auto-indenting, syntax highlighting, type hinting, and my preferred build tool (dune w/ inotify).
FWIW, this was much worse when I did it a couple of years ago. I think the ecosystem has made pretty big strides recently, much of which came from standardizing on dune as the canonical build system.
I typically install all what you listed, and tuareg. Worth noting that after installing these packages, they tell you how to configure your text editor, or you can choose to let them handle it for you automatically (IIRC). So yes, it is pretty easy to set OCaml up.
You are looking for "reason-language-server". You can download a VSCode extension with it from the VSCode "marketplace", and if you use a different editor like Vim you can use the language server with whatever vim language server plugin you prefer.
I will admit this is a little bit more confusing than would be ideal, because there is also an older ecosystem called "Merlin". If you are a reasonml beginner, avoid plugins that use Merlin.
Unfortunately I could never get RLS to work for me. I opened an issue on it, but it seems like the small group of people with the same issue haven't had the time/drive/knowledge to fix it. The maintainer doesn't want to fix it himself, but I don't blame him, he doesn't have the same error :/
Merlin requires opam, which is yet another tool beginners need to fight with. Reason support for Merlin was relatively recently merged, so it might work ok right now- but I couldn't get it to work with Reason the last time I tried. I agree it works fine with Ocaml.
I spent hours following the (multiple sets of disparate) instructions to set RLS up, and I was never able to get it working so I gave up on ReasonML. I like the concept, but it needs to be as easy to get up and running as Rust/Rust's "RLS".
> how _poor_ the experience was setting up an editor with autocomplete and type hints.
What? Merlin does all of these right out of the box, and supports emacs, vim and LSP [1]. The setup is basically `opam install merlin` and then `your-editor-package-manager install ocaml/reason-mode`
The most pragmatic reason to use TypeScript is for gradual typing of existing codebases, and that all JavaScript is valid TypeScript. You can introduce TypeScript over time and if it doesn't work out for your group, you can delete the TypeScript-specific annotations and go back to Babel. Or you can keep TypeScript to only specific parts of your application/library.
The most pragmatic reason not to use TypeScript is because it is static-only. And that is easy to forget. That is, within any arbitrary function X you have no guarantee the parameters passed to X are the type specified in TypeScript. There isn't a ton you can do about this except for to code very defensively where it matters.
I've used TypeScript for a number of projects professionally, introducing it among developers familiar and unfamiliar with static typing. I'm not really interested in going back or finding alternatives for now. It is a huge help in development and refactoring without a huge cost.
> and if it doesn't work out for your group, you can delete the TypeScript-specific annotations and go back to Babel.
You are joking right? Ever tried to do that with a large Typescript code base and then of course without the defensive strategy? It's a myth Typescript proponents really enjoy to believe.
> There isn't a ton you can do about this except for to code very defensively where it matters.
Bingo! That's what I always did and served me well on very large code bases. With Typescript I have to write about 20% more code(that can hide bugs too). Advantage? A bit more intellisense than VScode gives already out of the box, and of course for the junior and medior developers so they cannot mess up(types).
> Ever tried to do that with a large Typescript code base and then of course without the defensive strategy? It's a myth Typescript proponents really enjoy to believe.
You got me. I have not tried to do this in a large TS codebase.
> There isn't a ton you can do about this except for to code very defensively where it matters.
Of course you have this issue in pure JavaScript too. So it's not so much that TypeScript is worse it's just not as big an improvement in this regard as a compile-to-JavaScript language that gives you runtime type safety.
It's probably worth noting that that the "intellisense that vscode gives out of the box" is _also_ still delivered by TypeScript. Slap a `//@ts-check` into a js file and you'll find just where the typechecker thinks your jsdoc or usages are lacking or confusing (then maybe add a jsconfig with stricter settings than the default!). If you then fix those, you should be capable of getting _nearly identical_ completions in js and ts (because they really are just variants of the same language).
IMO, TS is all about static enforcement of inline documentation (types), just like how a linter enforces style. You can choose not to enforce it automatically... But at this point I don't get why you wouldn't? I wouldn't argue that style enforcement is best done by hand in the code review stage... So I don't see how further quality checks based on semantics derived from documentation would be any different.
In order to interopt, you're going to be using `any` a lot of places. You then have the illusion of sound types. You have all the extra work of sound types. But you do not have sound types and should not be relying on them.
> In order to interopt, you're going to be using `any` a lot of places.
This is not true. Types may be complex and out of laziness (which we do suffer from) there may be `any` types laying around. But you can set up eslint rules that forbid this and turn off implicit `any` at the TypeScript compiler level so you aren't bit by the defaults (which are not nice, for sure).
We have very few `any`'s now in our code and aim to get rid of all of them.
The new-ish `unknown` type is also very useful for interop work to force adding runtime checks where needed. Some of the Typescript defaults are slowly moving to `unknown` rather than `any` in the strictest compile modes.
If you turn off `any` on a gradually-typed codebase, everything will blow up because of the untyped parts. Gradual typing CANNOT be sound unless you dynamically typecheck everything (at which point you are just doing what JS devs do anyway).
> If you turn off `any` on a gradually-typed codebase, everything will blow up because of the untyped parts.
Again, that's not true. A JavaScript file's exports don't have `any` type. Any types during interop are inferred (maybe?) or require bindings. The use of `any` in bindings is your (sometimes valid) choice.
I think where these comparisons go wrong is tooling. I don't give a rodent's behind how nifty your new language is unless you've got excellent tooling, particularly, an easy to use step through debugger (and no, you still need a debugger when functional programming).
The second I think I'm doing printf() debugging I'm out.
Note, I'm only referring to actually getting work done here - academically, or if you're just dinking around, then it's all good.
I tried really hard to use ReasonML about a year ago but ran into a road block with the lack of async/wait (or equivalent) syntax. It's really cumbersome to have to go back to chaining callbacks again.
The problems I ran into with ReasonML (last year) is how scarce the ecosystem is and how incomplete the bindings.
I remember trying to fetch stuff. Fetch wrapper returns data in a type that is not convertible to anything. Ex. it returns Fetch.Uint8ArrayBuffer instead of Uint8ArrayBuffer. Because reasons.
And the compiler was entirely unhelpful. “X provided but somewhere required Y” is the best you could get for type errors. Somwhere... where?
I understand it’s a major sin to ever talk about syntax but a lot of ReasonML reads like someone’s cat walked across the keyboard
This project is interesting & the folks evidently involved & advocating invested a lot time + smarts but why bother with all this new cognitive load & new universe of subtle problems (bucklescript, interopt, external libs, etc)?
It's a matter of taste. Syntax isn't an issue if you're used to OCaml (which arguably has worse syntax -- semicolon as a list separator!) so there's no problem for that section of the userbase. I don't know how people coming from JS generally feel.
> "And while having that familiarity with JavaScript is nice, it means that every quirkiness that we love and hate about JavaScript is still there in TypeScript
> "... ReasonML is everything TypeScript tries to be (and a bit more) without all that JavaScript weirdness."
yeah, but what makes the author think that __INSERT NEW LANGUAGE HERE__ would not have other 'quirkiness'?
I came from low level language background but my "fun programming" went from Lisps to MLs and now I have difficulties going back to Lisps for projects. Always wanted to like Clojure but never had a need for JVM in anything I worked on.
My little brain can't keep up with too many things (in short term memory) at once, so more concise and expressive languages are better for me. Also, more sane languages are better (where it's harder to shoot yourself in the foot because you're not (yet) very good at the language).
The homoiconicity of Lisps allow me to think less about syntax. (function <args...>). And reading code is easier, since I see the operations first. Adding more advanced features (via macros) can give you lovely function chaining - threading, piping, etc.
Immutability in Clojure is also good for me because it allows me to learn and forget about functions. Black boxes are great things when you can trust that they are pure functions; just compose bigger black boxes and so on.
I don't do a lot of Clojure these days, but when I was using it professionally I could basically forget about JVM; JDBC was about the only time I interop-ed with Java. If you need some of the good things that JVM offers, great; if not, it's just a hosting environment. Same with Elixir and BEAM (although JVM and BEAM are very different and tailored for different problem sets).
But on topic, given a choice between ReasonML and JavaScript, it seems to me that ReasonML is more thought out, cleaner, safer, and a step in the right direction.
My little brain can't keep up with too many things either, which is why I only use statically typed languages.
Dynamically typed languages force me to keep type information in my head, and that's just not a good use of my brain when the compiler can be so much better than me at this.
I don't want to take a side in the dynamic vs static typing debate, specially since my favourite languages are Common Lisp and OCaml and it would be difficult for me to choose either, but I wouldn't say your argument of having to keep everything on the head holds if we consider the whole array of dynamic language offerings.
Compared to toys like Perl, Ruby, Python, etc., proper dynamic languages (Lisp, Smalltalk) have state of the art debuggers, browsing and discoverability tools and let you backtrack, experiment and tweak your program in an iterative fashion. They are indeed "little brain" friendly and the development experience matches or surpasses those of compilers and classical IDEs.
I am so much more productive in LispWorks than, say, IntelliJ+Java it isn't even funny.
- Because of a perfect balance between simplicity, elegance, and pragmatism.
- Because of "true" REPL. Having to be able to evaluate any expression without any ceremony, right from your IDE, with one or a couple of keystrokes is remarkably liberating. No language I have tried gives me that enormous boost when prototyping things. Even refactoring existing code is much faster, because of the tightened feedback loop. Only Smalltalk comes close enough, sadly, Smalltalk has been dormant in years, and I don't think it ever comes back as a pragmatic choice to build a business upon. Whenever anyone retorts "Python has REPL, Ruby too, etc." - it is just sad to see their ignorance. One has to give a heartfelt try to understand what makes Lisp REPL so unique.
- Because it is currently probably the best PL for building web-apps. You get real code-reuse, which even in NodeJS with JS/TS turned out to be an unfulfilled promise. You cannot merely share say data validation logic between back-end and front-end, even in NodeJS, let aside other languages. In Clojure, this turned out to be possible, even though you essentially might be running things on two completely different platforms - JVM and the browser.
- Because you have access to tons of libraries. Java and Javascript interop from Clojure and Clojurescript is lovely. Why re-invent the wheel if someone has already done it?
- Because of the fantastic community of genuinely inspiring, smart, and extremely friendly people. When you work with Clojure/Clojurescript, you always feel a step ahead of the crowd [working in other, more popular PLs]. So many things in different languages got inspiration from Clojure: destructuring, immutability, state management (redux, Elm), React hooks, etc. etc. And at the same time, Clojurists never feel shy and actively "borrow" good things from other languages, libraries, and tools. Clojure community of active members has probably fewer people than work for Google, but they generate and enhance so many awesome and cool ideas.
Rich Hickey is very convincing... he's just great at communicating in presentations, and he has plenty of good arguments why dynamic typing is the best for Clojure (and essentially for real-world problem solving).
My summary is: the extra burdens - tight coupling, code verbosity, custom type propagation for dealing with sparse or varying data - are just not worth the cost.
Also, I don't think there's much real evidence that static typing prevents production bugs. If you're doing decent testing, you'll catch those problems. And if your solution is fewer LOC and easier to reason about, you're more likely to not make type mistakes.
I think I don't mind lack types as much in immutable languages. Clojure and Erlang are like that. But they're also both simple languages with simple semantics.
One issue I have with Clojure is when I have to work with nested datatypes to perform transformations. It's just too hard to keep the shape of that data in your head. Types help with that.
Another important thing is modelling business domains with types. By this I really just mean record and variant types and not some advanced type-fu. It really helps seeing what kind of data your application manages.
>One issue I have with Clojure is when I have to work with nested datatypes to perform transformations. It's just too hard to keep the shape of that data in your head. Types help with that.
And in Ruby or Java, I have the same problem with objects.
No matter what your method of interaction, keeping up with levels of abstraction is hard.
> One issue I have with Clojure is when I have to work with nested datatypes to perform transformations
Out of so many "excuses" to dislike Clojure I've heard over the years, the first time I see something like this, and this sounds wrong for a few reasons. Dealing with nested transformations is not harder or easier in Clojure, it is just different, due to immutable data structures.
Besides, if you need to deal with deeply nested data perhaps it has to be broken down into smaller pieces. Maps are extremely composable in Clojure.
There are libraries that can help you with transformations, you can "walk" the structure, you can use zippers, you can use Specter (a library that I haven't find a use for in over three years of writing Clojure).
Modeling business domains with types is a valid point though. However, I find exactly that is the reason why using Clojure for building real business apps is so much simpler and faster. You are not restricted by the boundaries of whatever type system you use. You don't get stuck in analysis paralysis trying to prototype things in an overly complex type system. At the same time you don't necessarily have to throw the types away. Type systems do help and absolutely have certain benefits, etc. And Clojure has a pretty nice one called Clojure.Spec. In Spec you can for example conform that your function returns specific shape of data. You can use specs to validate inputs, produce human readable error-messages, generate fake data and use it for example for property-based testing. Specs can be shared between different systems, between front-end and back-end, etc.
Clojurescript HAS types. It has pretty awesome type system - Clojure.Spec. You can use Spec to enforce all sorts of things in your functions. You can use them for data validation, for data generation, for human-readable error messages, etc. Specs can be shared between server and front-end.
After trying things like Elm, Purescript, ReasonML and especially Clojurescript, yes, Typescript does feel like a complete fad. It seems like people get excited about it, ignorant to the fact that there are better alternatives.
I’m not a fan of TypeScript, and never heard of Reason, but this post makes me like TypeScript more. I prefer the fact it’s more explicit and less magic.
The examples in TS are very easy to understand where Reason just looks like magic and I have to think more about what it might be doing. I don’t see a benefit to Reason over vanilla JS.
I'd argue that's because ReasonML/Buckle is a completely different language whereas Typescript is a superset of JavaScript. I don't believe that one is inherently more legible than the other, but you might have trouble reading Reason if all you know is C-style languages.
You need to study ML based languages like Haskell and reason to see why reason is actually more explicit then type script. It takes some effort but in the end you'll have a better understanding of programming in general.
Typescript is missing the Variant type primitive so it has to be written in a roundabout way to imitate that primitive (and it most likely isn't type checked correctly either). The extra code you are writing in TS is not explicit... a better term is obfuscates.
> Why don’t we have to write types in ReasonML? Because of the powerful type system that allows for incredible type inference.
You mean the restricted type system that allows for principal type inference? ML's type system is much less powerful than Haskell's or Scala's, precisely to allow type inference (e.g. no overloads). The type inference itself isn't very powerful or incredible either, it works just for the exact type system offered by ML.
Obviously nobody likes this, which is why ML offers a number of hacks to allow more powerful type system hackery - e.g. open sums ("polymorphic variants" and also open exception types), HKTs (modules), GADTs, row types & subtyping (objects). Although IMO it would be better to incorporate all these hacks in the core language itself, and only run type inference on the bits where it's possible (but of course tradeoffs would be necessary to avoid running into halting problems etc.).
Yes, that sentence should've probably been "Why don't we have to write types in ReasonML? Because of the powerful type inference system". But I think comparing to Haskell is kind of missing the point: you can't use that for front-end web development. Also, I'm not sure what you mean by saying those "hacks" aren't part of the core language.
By “hacks” I mainly meant that they don’t play that nice with type inference... type annotations are sometimes needed. Also that they were added to the language later, in order to make the type system more powerful.
These are not hacks, subtyping and GADT are part of the core language
> ML's type system is much less powerful than Haskell's or Scala's
Yes, but most of the fancy stuff is located in the module language, and it doesn't lack power, it lacks implicitness (until modular implicits would land).
> ML's type system is much less powerful than Haskell's or Scala's
That's arguable. What's not arguable is that every OCaml compiler runs circles around GHC and scalac in terms of compilation speed. If you like not waiting for the compiler, then you should like OCaml.
I inherited several Flow codebases, which is from the same guy/group as Reason at FB, and attempted to do the same goal of "bah explicit type annotations, let's infer everything".
This nearly ruined the codebases and we ended up converting them all to TS because:
A) tactically, flow's global inference sucked, so many things that devs thought were typed, were not. I get they're trying a do-over and using OCaml to solve that this time around, which I agree is fundamentally a better approach.
B) strategically, explicit type annotations (i.e. at method boundaries) is a _feature_ for dev readability and comprehension and not a burden to be avoided. I get that Flow/Reason _can_ add these type defs, but many new devs see posts like this, think "nice, less chars to type!", and now write a 100k LOC codebase with a pervasive lack of any type defs.
Tldr, I think TS's explicit types at boundaries is actually best practice for programming-in-the-large.
> explicit type annotations (i.e. at method boundaries) is a _feature_ for dev readability and comprehension and not a burden to be avoided
The problem is not the type inference, but tooling. If your IDE can, when you're coding can 'show-type-at-point' and your online github style code review tool shows the type of any expression, argument, ... when you hover over it, you no longer have this problem.
A was a bug. Previously Flow when certain errors happened internally, Flow would "demote" a type into `any`, but that's been fixed for quite some time now.
Many modern languages support type inference to some degree. The special thing about ReasonML is that the type inference is much more robust and complete.
In practice you don't need to provide any types annotations at all at any point in your program and you should still get the same safety benefits.
Every value in your code will automatically have one single type assigned to it based on how it is being used. When the compiler finds contradictions it will let you know about it. On the other hand, Typescript will try to unify the type to some sort of Any, which is probably not what you want.
Why do people care about this? Writing type annotations is not that much work, but they make your code more readable.
I can see the argument for it when you have unspellable nested generic types, or when you don't want to write the same thing on both sides of an assignment. Simple type inference like in TS gives you that.
Having really smart type inference makes your compiler slower and more complicated. What's the real-world benefit?
> Typescript will try to unify the type to some sort of Any, which is probably not what you want.
Not really, unless you push the type inference beyond its limits, in which case you should just change your code.
> Why do people care about this? Writing type annotations is not that much work (...)
I personally care about this when I'm prototyping something. Not having to write types means that I can simply write what's on my mind. The benefit of good type inference is that the compiler can still help me highlight any mistakes I made during this process.
Another benefit is that some production code can get very complex. Having to type every single value is just too cumbersome and distracting. It contributes to boilerplate and increases cognitive load, in my opinion.
> Having really smart type inference makes your compiler slower and more complicated.
I would actually disagree with this. First of all, ReasonML's compiler is absurdly fast. No, seriously try it. Sometimes I go and double check the generated JS code just to be sure it actually did anything.
Regarding the "more complicated" part – the only reason why full type inference works is because it is based on a very solid theoretical foundation. It might be somewhat "complicated", but it will never be as complicated as something ad-hoc that needs to account for all inconsistencies that exist in untyped languages like JavaScript.
> It contributes to boilerplate and increases cognitive load, in my opinion.
In my experience with OCaml, I found that knowing the types of my variables reduce the cognitive load of trying to infer the types myself when reading the code, so despite OCaml supporting type inference, I use explicit type annotations almost everywhere.
Being a Vim/Merlin user I normally just type `<Leader>t` to see the type of the value under the cursor (this maps to `:MerlinTypeOf` if I recall correctly).
What I do use type annotations for is debugging. If the compiler finds a type error, in some situations it helps adding type annotations to find where the actual error is.
If you are writing in a Functional paradigm you'll most likely create a lot of small functions that perform very direct actions and then chain them all together to get a more robust operation. When I write Haskell I'll often annotate the larger functions and keep all the smaller ones to auto-generate. They will either determine the type based on the larger function's definition or by operations inside the smaller ones. No point in me writing an extra line for a one line function when its obvious the type.
One problem with people selling ReasonML's type inference is that it's not depicting the actual developer experience.
The first thing to note is that you do interact with types to build a mental model! The editor will show them to you as you move your cursor around the code. It will do that for every single value. Just move the cursor over that `requestContext` argument and voilà, it'll show you the type.
Another important aspect is that you still have to define types. Have a person object with a bunch of fields? You do have to type annotate every single field. Have multiple actions to handle in reducers? You need to tell ReasonML in advance what those are using a variant type.
And finally, if you care about composition/abstraction you should provide interfaces for modules. This requires writing types for all functions explicitly to ensure that you are exposing a correct protocol. This is optional, but it helps both the author of the code and the consumers.
Type-inference is just a nice to have feature for the low-level implementation details.
Nothing stops you using type annotations in ML, and good developers will do so where it is useful. But it doesn't require them in cases where they are redundant, for instance when you have a parameter `foo` of type `Foo`.
IMHO the biggest reason to use TypeScript rather than Reason is because, in the end, it's still javascript and it stays within the same ecosystem. As great as Reason is, it's adding too many dependencies. The only time I'd go with Reason instead of Typescript is if the developers on the team are already proficient with OCaml. It's going back to the "Use boring technologies even though some things suck as it's better to deal with known issues than unknown issues; the grass isn't always greener with new technologies, just different things that suck".
You can rename a JS file to a TS extension and it's TypeScript. The annotations are replaced with whitespace when JS is generated, it doesn't result in different code.
Being as close as possible to JS for transpilation is a design goal of Typescript, but the compiler still does more than just strip away the types if you use certain language features.
If it was just about stripping away types, then there wouldn't be a need for a compiler and the type checker could be a separate program.
You can already run Typescript type checker separately from a type stripper today, though. Typescript has babel plugins for exactly that. Almost all webpack best practices for Typescript involve the fork-ts-type-checker plugin, running type checks in a separate thread from the transpile phase.
At this point almost all of what Typescript compiles (transpiles) that isn't just "type stripping" is some form of downleveling from current ES standards to older ES versions and is almost all entirely optional. There's also nothing stopping you from leaving it all to Babel to do it instead of having Typescript do it, if you wish.
(Though personally, I think Typescript is a lot lighter weight a solution than most Babel presets and tslib (the optional dynamic link version of the inline downlevel helper functions TS emits) is a lot smaller than Babel's equivalents in core-js.)
I don't think this refutes my original point. Asm.js is exactly a low-level subset of JavaScript.
ReasonML doesn't compile to Asm.js. Most languages that compile to JavaScript also want to interop with JavaScript APIs and other high-level JavaScript code. Other posts in topic discuss this exact difficulty of combining ReasonML with the JS ecosystem.
TypeScript interops with JavaScript much easier than ReasonML. I can’t stand BuckleScript and all the work required to interop with the rest of the JS ecosystem.
This is exactly the reason why Typescript took off and none of the other compile-to-JS languages did (other than a brief flash by CoffeeScript). Dart had a very similar problem as Reason - it wasn't Javascript. The interop between Dart and JS libraries was just too much of a pain to deal with, where in Typescript everything just worked.
Since building your own ecosystem that rivals the JS ecosystem for libraries is an extremely difficult task, any candidate to replace JS must have good interop with its libraries to be successful.
None of these are worth switching to a fringe ecosystem for.
The compile-example is misleading. Clean build times don't really matter. Iteration times with "tsc -w" would've likely been well under a second.
The boiler-plate in the redux example could be reduced, but redux itself is extremely boilerplate-heavy when written in this explicit style. It would be interesting to see how the ReasonML typesystem gets in the way of metaprogramming.
At work our TS project with a thousand files takes 30mins on CI, incremental save takes at least 20 second. Half of the time makes the machine run out of memory.
The only problem that I have found is that in some situations it doesn't report errors correctly when adding new dependencies. Refreshing the window or rebuilding the project usually helps.
On the other hand, the compiler feels very solid. It can be annoying in the beginning because of how disciplined the code needs to be, and the error messages can be hard to understand.
After getting used to that, the dev ex is really smooth specially during complicated refactoring, the compiler and the IDE make things almost boring.
Why should we concern ourselves with psychological processes of the mythical average developer? The question is, whether this language is appealing to you.
I suppose if you're working on a small pet project that nobody else is going to see. If you're working on something with a bunch of other people you're likely to care more about what the mythical average dev thinks.
This kind of comments make me really sad. Most ReasonML developers are actually "JavaScript developers".
ReasonML was specifically designed for JavaScript developers. Being JavaScript-friendly is in it's DNA really. I even see ReasonML a language that was specifically designed to work with React.
I can understand why you would feel this way though. I think the main problem is that ReasonML introduces new concepts that simply do not exist in JavaScript. Learning those concepts is hard. But if it wasn't hard, would you be learning anything or just writing JavaScript in a slightly different way?
Are there any specific examples of things that look too "distant" from JavaScript?
I'll finish this by saying that ReasonML will make you a better JavaScript developer. As will probably learning any other language that challenges the way you think.
> I'll finish this by saying that ReasonML will make you a better JavaScript developer. As will probably learning any other language that challenges the way you think.
I wholeheartedly agree! But a lot of people don't want to be challenged by the language when there are already plenty of other challenges in their personal and professional lives.
With TypeScript you don't need to think half as much, and the thing your boss generally cares about (whether the feature shipped) gets done.
> With TypeScript you don't need to think half as much
That seems a bit dangerous, to be honest. TypeScript has an unsound type system. If you don't think carefully, you might end up shipping runtime type errors.
228 comments
[ 3.4 ms ] story [ 267 ms ] threadI read up on reducers for Redux and Cycle.js, but avoided using them. The Typescript examples are so cumbersome; I'd rather do something else where my code is more understandable.
But in ReasonML it's elegant. It's awesome! I'd choose to use that.
Sure they do the same thing. But as the developer who has to commit to reading and maintaining this code, I know which I'd rather write and come back to in 18 months' time.
It shouldn't be a surprise that React works well in ReasonML - the first prototype of ReactJS was written in StandardML before the creator ported it to JS (https://reasonml.github.io/docs/en/what-and-why.html)
[1] https://www.typescriptlang.org/docs/handbook/advanced-types....
I wouldn't want to work on a project where everyone was creating object literals and passing them to dispatch everywhere.
If you insist on having the action creators, they can easily be written as one liners each, which is a lot less bloated than the original example.
What's the issue with passing object literals? I know it looks a bit hacky and messy, but if it's typesafe (which it is) then it doesn't seem like a real issue.Let's say you need to update your function signature with an additional, optional parameter with a default value. You can go to the definition of the action creator and change it. Since you've defined an interface for the action object, finding reducers that use it will be reliable and easy.
If such a creator doesn't exist, you must grep your codebase for every instance of the object and ensure they deal with the optional bit. Murphy's Law guarantees that someone will have decided to procedurally generate the object making finding every instance very hard. Next, you have that reducer lookup, but you don't have a shared interface, so you're stuck in grep land once again to find all the reducers.
Finally, copy-pasting an object literal all over generally results in a larger bundle than reusing an action creator which is smaller and also minifies better.
Using Murphy's Law is not very convincing. Even with action creators Murphy's Law guarantees someone would have just constructed the objects manually anyways. Having to grep the tag is not ideal, but it's not likely to be a problem in practice.
I doubt that the bundle would be significantly larger (especially after compression). Using actions creators introduces additional (function call) overhead too (and that would be insignificant as well).
[0] -- https://redux.js.org/recipes/usage-with-typescript
While I'm sure the author did not intentionally try to make TypeScript look bad, (imo) they didn't put enough effort to make it look good either. There's a lot more to TypeScript that this post fails to mention. ReasonML is a good language (I think OCaml was just fine though), but TypeScript is good too.
The author says "ReasonML is everything TypeScript tries to be (and a bit more) without all that JavaScript weirdness.", but I strongly disagree. Embracing "all that JS weirdness" is the whole point of TypeScript, and what makes it so successful. Unlike most typed languages, TypeScript (for better or for worse) adapts its type system to the developer's code, not the other way around. This is why its type system is much more expressive than many other type systems including ReasonML's.
Which is a long way around to: the pattern in that recipe is a good one, it's quite exhaustive in making sure everything is typed, and it's quite familiar to almost any Redux dev you will meet whether they come from JS or TS. You can get away with other patterns/recipes if you want looser typing or "looser redux", based on the compromises you are willing to take, but currently there's no strong recommendation for any such alternative pattern because the common recipe is "good enough", even if it has lots of little bits of extra "ceremony". (Ceremony isn't necessarily a bad thing, sometimes those rituals are helpful in getting your thoughts down and making sure you have everything you need planned out.)
But that's also part of why the example in the author's post is unfair to Typescript. It's as strict on types as the Typescript pattern, but it's "looser redux". It sort of has action creators, but those aren't proper functions from a JS perspective because they are type constructors that don't properly exist in JS. They also don't exactly correspond to/produce the usual sort of "plain JS objects" that Redux expects when dispatching ({ type: "DISCRIMINATOR_STRING", … }), which will worsen and even in some cases may break Redux debug tools/experiences.
(Semi-related, there is a Stage 1 proposal for pattern matching in front of TC39 that would also help reduce the switch case that TS needs to pattern matching closer to the ReasonML example. TS sticking to Stage 3+ proposals now means that it won't get some of these sort of nice to have things until JS does.)
> It is also interesting to look at what javascript was generated by ReasonML
This is where BuckleScript should probably be mentioned first. TypeScript is both the name for the language and the tsc compiler, since it's a single project. But with BuckleScript that's not the case.
> BuckleScript is the tool that transpiles ReasonML to JavaScript.
There's also the older js_of_ocaml (JSoO) project, and there's more great Reason/OCaml tooling for native, like esy and dune! It's really exciting, but these small details are rather interesting and it's important to highlight them to avoid confusing beginners.
Dune is the build system which has become the standard in the Ocaml community. It can use the native compiler or js-of-ocaml to produce javascript. It doesn't support BuckleScript however.
Esy is a tool which allows you to manage your project dependencies. It can pick package from both opam and npm and uses Dune as a build system. It's actually very good.
As Facebook mostly pushes ReasonML towards Javascript developers, they mostly advertise BuckleScript. From what I have seen, the Ocaml community mostly does not care about Reason. They had no issue in modifying tools to support it and noone is going out of its way to hinder it but there is no particuliar enthusiasm towards it.
For JS it's a step forward, for OCaml it seems to be a step back.
There's no particular enthusiasm towards quite a lot of things in the OCaml community :-) there are even some holdouts who don't like or use dune/opam. I wouldn't take that as a point against ReasonML/BuckleScript, which is bringing devs en masse to OCaml who would have never looked at it before.
Frankly, of the time I spend reading documentation for my (Web) project right now it breaks down into 5% reason-urql (thanks, BTW, can't wait for 1.0), 10% reason-react, 15% ReasonML, 70% bucklescript.
Even the ReasonML docs that I am reading are pretty heavy on the bucklescript.
They are serious problems. You end up writing a ton of [@bs.... ] annotations to get things to work together that ultimately result in runtime errors.
I hate battling a compiler (with sometimes quite cryptic errors, particularly identifying syntax errors) only to find that I can't actually free myself from the same type of errors I'd see with regular old JavaScript.
I can dig through my discord history and give more specifics. But given the massive learning curve and poor tooling that exists these days it's a step backwards from mainstream production JS development.
That said, the community has really got some wonderful folks out there, and is very helpful. I hope they can figure out interop with basic JS like promises. And tooling. And let me use async/await. And make bundle splitting easier to implement.
[0] - https://github.com/OvermindDL1/bucklescript-tea
Both are transpilers for OCaml -> JavaScript.
EDIT: Nevermind [1]
https://github.com/OvermindDL1/bucklescript-tea/issues/45
Regarding bindings -- which external libraries were you using? Component libraries written for React could be a fair amount of work to write bindings for, but I anyway prefer hand-rolling components with Tailwind, so that hasn't been a problem personally.
Bindings were indeed difficult when I started out - Javascript can invoke functions in a myriad ways: new xyz(), object.key, object.key(), module defaults, Math.abs etc. We have to map them to OCaml's semantics (easy), and figure out the BuckleScript macros for them (easy with practice). I recently wrote a bunch of bindings for Adobe XD, and it was surprisingly fun.
I first used my own minimal bindings to Mithril, but then later moved to an Elm Architecture implementation for bucklescript: bucklescript-tea. After being on a deadline and either completely misunderstanding or finding significant show-stopping bugs in bucklescript-tea's virtual dom implementation, I reimplemented the parts of the API I'd used from bucklescript-tea with a minimal alternative that called out to snabbdom for the vdom stuff. This was mainly as a learning exercise, which took all of about two afternoons of fairly relaxed work.
The only places I have needed to use annotations with this approach is in my modules that call out to JavaScript. This is really unavoidable, and you'd surely find the equivalent information would have to be provided if you used F# or PureScript, or your compiler simply won't know the types.
I'm sure my approach, with layers on layers of functions to build virtual dom nodes, is certainly not going to win any benchmarks, and it's certainly not novel – but it's good enough, and my productivity is up. I can leave a project for months and be relatively unsurprised by what I find when I return – because there's usually only one sensible way to do things. Everyone's different, though, and I fully respect it may not work for all.
The key lesson I've learnt is that interop is painful because the some of the bucklescript types do not neatly map how you would necessarily imagine to javascript types. But if you do this at the edges, and write sufficient wrappers, you won't have any problems.
The biggest pain point for a while has been that bucklescript is based on an old version of OCaml – and there have been some very nice additions to the OCaml standard library since then. That said, I think it's excellent that we've got two OCaml -> JS pathways in the OCaml world now.
This seems like a massive headache, what about JSON? or accessing dictionaries? Do you need to parse every js datastructure to a ReasonML record?
As opposed to JS, where really all these things are overloaded onto the same data structure ( object ).
> Although I didn’t write any types by hand, the arguments of this function are typed. Why don’t we have to write types in ReasonML?
Is perfectly fine for typescript too. As for: So I stopped reading there.They're separate concepts. Maybe I want to ensure that I'm receiving a product as it makes no sense to invoke formatName(...) on other things with names like a Cat or Person.
And the generic "thing with a name" one is possible in TypeScript too:
And this happens all the time.
In this case it truly is a Product and if you want to reuse a field name you'll need to enclose that type in a module.
Row polymorphic -- "thing with a name" -- is possible through the object system which is accessed through a different syntax, and works like go interfaces in practice.
Yes.
> does it infer at use time that the formatName(...) invocation is valid because the argument has a "name" property?
No. OCaml does have a structural object system (https://v1.realworldocaml.org/v1/en/html/objects.html) and it is available to reason, but it's syntactically funky (https://reasonml.github.io/docs/en/object) and as far as I can tell TFA does not use it.
> They're separate concepts.
Yes. Which is why OCaml (and thus Reason) separates it[0] from the nominative "structures".
> Maybe I want to ensure that I'm receiving a product as it makes no sense to invoke formatName(...) on other things with names like a Cat or Person.
You're really deep into this strawmanning thing.
[0] pretty completely, you can't use nominative types in a structural context, they are not compatible. In fact structural types are sets of methods, something which doesn't exist on the nominative side.
The TS version has zero type safety (compiler infers any), whereas the RML version will only accept numeric type arguments.
I guess a clearer distinction between the two can be nice, but then how does that compile to javascript?
The typescript version will be "inferred" as any, and it will refuse to compile if you set `noImplicitAny`.
> So I stopped reading there.
Because you could not handle the truth?
At no point in time anything insightful preceded or followed those words...
The equivalent of
in Typescript is more something like :[0] -- https://redux.js.org/recipes/usage-with-typescript
I'm comfortable with TS at a general level (declaring interfaces for API types, reducers, and React component props), but a lot of folks seem to go crazy trying to get 100% type coverage. I personally aim for more of an "80%" sweet spot for typing - trying to trade off overall benefit with not trying to tie myself in knots adding ridiculous type declarations.
I actually just wrote a tutorial page for Redux Starter Kit that shows how to use it with TS and `useDispatch` [1], but had someone point out that one of the examples I was using for thunks was losing a bit of type safety [2]. This stuff is hard :)
[0] https://github.com/reduxjs/redux/pull/3201
[1] https://redux-starter-kit.js.org/tutorials/advanced-tutorial
[2] https://github.com/markerikson/rsk-github-issues-example/pul...
ReasonML has better theoretical foundation. The variant type is a primitive type that TS and many other languages miss.
I believe this is closer to polymorphic variants than to actual sum types (global namespace like strings, comparison by name). You can tighten it a bit by switching to symbols instead of strings, which would guarantee unique tags, getting closer to classic variants. However TypeScript isn't big on nominal types, and for a good reason: the node module system for example can easily cause two versions of a module to be instantiated in the same program which in turn would cause two different and incompatible instances of the symbols and symbol types.
Nevertheless I would still say that FP-like patterns are supported reasonably well.
However If it is string comparison on the TS level then that means it isn't type checked. Are you sure this is what you mean? That means I can add a new case for string "asdasfsdfds" and TS won't throw a type error.
The variant type is not exclusively an FP paradigm.
Therefore you are right - its not quite a string at the TS level - its a subset of string.
p.s. you can make unions of any primitive values in TypeScript:
or between any type and other values, really: TS is not always that smart about it (compared to Flow), but sometimes it works out quite well :)IMO, the beautiful type system is nice, but the DX with TS is much more productive than Reason or OCaml.
PS: Do you think my comment is dumb? Reason/OCaml are easy to set up? It's as easy as 1-2-3? Please link me tutorials or resources that prove me wrong. I'd love to get started, but DX has been the major hangup again and again.
Microsoft has paying customers who are devs, and FB doesn't. That explains a lot.
[1]: https://www.bloomberg.com/company/press/open-source-at-bloom...
[2]: https://reasonml.github.io/docs/en/what-and-why
EDIT: haha here it is https://github.com/facebook/flow/issues/4785
Nobody should be using Flow.
Assignment is still there though (ie you can still assign a `Cat[]` to an `Animal[]` and push a `new Dog()` into it).
When you mentioned "covariant param passing" I assumed you were referring to the long-lasting issue about callbacks, hence my original comment mentioning that that issue has been fixed. It's not useful to differentiate between "assigning a value to a variable of a certain type" and "passing a value to a function parameter of a certain type" because those are identical.
(I don't ever have to have any `any` in my ReasonML code, but it was unavoidable with Flow.)
1. https://reasonml.github.io/blog/2017/09/08/messenger-50-reas...
2. https://github.com/facebook/flow/issues?utf8=&q=is%3Aissue+i...
Over the time I began more and more appreciate Microsoft's "boringness" (which is debatable itself) because their tools are real products, not marketing tools for developers. What matters more for serious project is tools quality, not few hot CS ideas loosely implemented on a knee.
Go was created to solve Google's problems with C++ and engineering at scale
The fact that TypeScript wasn't created to solve a big issue internally at Microsoft is exactly what makes me skeptical of it and personally I view it as a trojan horse, just like VSCode. Sure, today it is done for benevolent reasons, maybe, but what about tomorrow? What if that trillion dollar market cap starts tanking? Then The benevolent VSCode and TypeScript ecosystems become levers to pull in the great big machine.
I would almost feel better if I knew it's stated explicit purpose upfront and what Microsoft's long term goals to monetize the ecosystem/platform were, because then I know what I am signing up for.
AFAIK it was, the issue was the poor quality of Javascript development they were having and their inability to really get good tooling and static analysis. Then it kind of exploded as other people said "That's cool" and they then pushed it out as open source so that if they did need to let it go it wouldn't be totally dead in the water.
VSCode was started to see if they could build a good web application for development. Portions of it directly power a number of tools of Azure (on portal.azure.com), portions of it also directly powered the Dev Tools inside IE10+ and Edge "Classic". Bundling the web app into an Electron container after it had already paid for its own engineering efforts several times over as reusable sets of components for multiple large web apps Microsoft needed hardly seems like a "trojan horse" to me.
Technology is a stretched term for ReasonML. It's a simple syntactic sugar for OCaml. It uses standard OCaml compiler and standard JS transpilers, like js_of_ocaml and bucketscript.
FWIW, this was much worse when I did it a couple of years ago. I think the ecosystem has made pretty big strides recently, much of which came from standardizing on dune as the canonical build system.
I installed bs-platform and still got errors from my IDE about bs-platform missing.
Which was strange, since I used the bs-platform CLI tools.
Turns out you also had to have bs-platform installed locally, but the error messages didn't say anything about this, just "bs-platform not found".
I will admit this is a little bit more confusing than would be ideal, because there is also an older ecosystem called "Merlin". If you are a reasonml beginner, avoid plugins that use Merlin.
Maybe it will be ironed out someday...
If I recall correctly, everyone managed to set things up swiftly, with an exception of one Windows user who had some editor issues.
What? Merlin does all of these right out of the box, and supports emacs, vim and LSP [1]. The setup is basically `opam install merlin` and then `your-editor-package-manager install ocaml/reason-mode`
[1] https://github.com/ocaml/merlin
- npm install --global bs-platform # (you need to ensure you have write access to the global install path)
- Fire up VSCode
- Install the reason-vscode extension
- Run `bsb -init my-project -theme react-hooks` (assuming you want to scaffold a new ReasonReact project)
More details at https://reasonml.github.io/docs/en/editor-plugins
The most pragmatic reason not to use TypeScript is because it is static-only. And that is easy to forget. That is, within any arbitrary function X you have no guarantee the parameters passed to X are the type specified in TypeScript. There isn't a ton you can do about this except for to code very defensively where it matters.
I've used TypeScript for a number of projects professionally, introducing it among developers familiar and unfamiliar with static typing. I'm not really interested in going back or finding alternatives for now. It is a huge help in development and refactoring without a huge cost.
You are joking right? Ever tried to do that with a large Typescript code base and then of course without the defensive strategy? It's a myth Typescript proponents really enjoy to believe.
> There isn't a ton you can do about this except for to code very defensively where it matters.
Bingo! That's what I always did and served me well on very large code bases. With Typescript I have to write about 20% more code(that can hide bugs too). Advantage? A bit more intellisense than VScode gives already out of the box, and of course for the junior and medior developers so they cannot mess up(types).
You got me. I have not tried to do this in a large TS codebase.
> There isn't a ton you can do about this except for to code very defensively where it matters.
Of course you have this issue in pure JavaScript too. So it's not so much that TypeScript is worse it's just not as big an improvement in this regard as a compile-to-JavaScript language that gives you runtime type safety.
IMO, TS is all about static enforcement of inline documentation (types), just like how a linter enforces style. You can choose not to enforce it automatically... But at this point I don't get why you wouldn't? I wouldn't argue that style enforcement is best done by hand in the code review stage... So I don't see how further quality checks based on semantics derived from documentation would be any different.
This is not true. Types may be complex and out of laziness (which we do suffer from) there may be `any` types laying around. But you can set up eslint rules that forbid this and turn off implicit `any` at the TypeScript compiler level so you aren't bit by the defaults (which are not nice, for sure).
We have very few `any`'s now in our code and aim to get rid of all of them.
Again, that's not true. A JavaScript file's exports don't have `any` type. Any types during interop are inferred (maybe?) or require bindings. The use of `any` in bindings is your (sometimes valid) choice.
> Gradual typing CANNOT be sound
TypeScript isn't sound [0].
[0] https://github.com/Microsoft/TypeScript/issues/9825
The second I think I'm doing printf() debugging I'm out.
Note, I'm only referring to actually getting work done here - academically, or if you're just dinking around, then it's all good.
I remember trying to fetch stuff. Fetch wrapper returns data in a type that is not convertible to anything. Ex. it returns Fetch.Uint8ArrayBuffer instead of Uint8ArrayBuffer. Because reasons.
And the compiler was entirely unhelpful. “X provided but somewhere required Y” is the best you could get for type errors. Somwhere... where?
ReasonML is very very early on adoption curve.
This project is interesting & the folks evidently involved & advocating invested a lot time + smarts but why bother with all this new cognitive load & new universe of subtle problems (bucklescript, interopt, external libs, etc)?
Is ES6 + Typescript in 2019 really so awful that all this effort is required to build rich applications for web?
Perhaps ReasonML is like “the big guns”, but what situation makes it necessary/important/better to use?
> "... ReasonML is everything TypeScript tries to be (and a bit more) without all that JavaScript weirdness."
yeah, but what makes the author think that __INSERT NEW LANGUAGE HERE__ would not have other 'quirkiness'?
I'd still rather use ClojureScript, though.
I came from low level language background but my "fun programming" went from Lisps to MLs and now I have difficulties going back to Lisps for projects. Always wanted to like Clojure but never had a need for JVM in anything I worked on.
The homoiconicity of Lisps allow me to think less about syntax. (function <args...>). And reading code is easier, since I see the operations first. Adding more advanced features (via macros) can give you lovely function chaining - threading, piping, etc.
Immutability in Clojure is also good for me because it allows me to learn and forget about functions. Black boxes are great things when you can trust that they are pure functions; just compose bigger black boxes and so on.
I don't do a lot of Clojure these days, but when I was using it professionally I could basically forget about JVM; JDBC was about the only time I interop-ed with Java. If you need some of the good things that JVM offers, great; if not, it's just a hosting environment. Same with Elixir and BEAM (although JVM and BEAM are very different and tailored for different problem sets).
But on topic, given a choice between ReasonML and JavaScript, it seems to me that ReasonML is more thought out, cleaner, safer, and a step in the right direction.
Dynamically typed languages force me to keep type information in my head, and that's just not a good use of my brain when the compiler can be so much better than me at this.
Compared to toys like Perl, Ruby, Python, etc., proper dynamic languages (Lisp, Smalltalk) have state of the art debuggers, browsing and discoverability tools and let you backtrack, experiment and tweak your program in an iterative fashion. They are indeed "little brain" friendly and the development experience matches or surpasses those of compilers and classical IDEs.
I am so much more productive in LispWorks than, say, IntelliJ+Java it isn't even funny.
- Because of "true" REPL. Having to be able to evaluate any expression without any ceremony, right from your IDE, with one or a couple of keystrokes is remarkably liberating. No language I have tried gives me that enormous boost when prototyping things. Even refactoring existing code is much faster, because of the tightened feedback loop. Only Smalltalk comes close enough, sadly, Smalltalk has been dormant in years, and I don't think it ever comes back as a pragmatic choice to build a business upon. Whenever anyone retorts "Python has REPL, Ruby too, etc." - it is just sad to see their ignorance. One has to give a heartfelt try to understand what makes Lisp REPL so unique.
- Because it is currently probably the best PL for building web-apps. You get real code-reuse, which even in NodeJS with JS/TS turned out to be an unfulfilled promise. You cannot merely share say data validation logic between back-end and front-end, even in NodeJS, let aside other languages. In Clojure, this turned out to be possible, even though you essentially might be running things on two completely different platforms - JVM and the browser.
- Because you have access to tons of libraries. Java and Javascript interop from Clojure and Clojurescript is lovely. Why re-invent the wheel if someone has already done it?
- Because of the fantastic community of genuinely inspiring, smart, and extremely friendly people. When you work with Clojure/Clojurescript, you always feel a step ahead of the crowd [working in other, more popular PLs]. So many things in different languages got inspiration from Clojure: destructuring, immutability, state management (redux, Elm), React hooks, etc. etc. And at the same time, Clojurists never feel shy and actively "borrow" good things from other languages, libraries, and tools. Clojure community of active members has probably fewer people than work for Google, but they generate and enhance so many awesome and cool ideas.
My summary is: the extra burdens - tight coupling, code verbosity, custom type propagation for dealing with sparse or varying data - are just not worth the cost.
Also, I don't think there's much real evidence that static typing prevents production bugs. If you're doing decent testing, you'll catch those problems. And if your solution is fewer LOC and easier to reason about, you're more likely to not make type mistakes.
One issue I have with Clojure is when I have to work with nested datatypes to perform transformations. It's just too hard to keep the shape of that data in your head. Types help with that.
Another important thing is modelling business domains with types. By this I really just mean record and variant types and not some advanced type-fu. It really helps seeing what kind of data your application manages.
And in Ruby or Java, I have the same problem with objects.
No matter what your method of interaction, keeping up with levels of abstraction is hard.
Out of so many "excuses" to dislike Clojure I've heard over the years, the first time I see something like this, and this sounds wrong for a few reasons. Dealing with nested transformations is not harder or easier in Clojure, it is just different, due to immutable data structures.
Besides, if you need to deal with deeply nested data perhaps it has to be broken down into smaller pieces. Maps are extremely composable in Clojure.
There are libraries that can help you with transformations, you can "walk" the structure, you can use zippers, you can use Specter (a library that I haven't find a use for in over three years of writing Clojure).
Modeling business domains with types is a valid point though. However, I find exactly that is the reason why using Clojure for building real business apps is so much simpler and faster. You are not restricted by the boundaries of whatever type system you use. You don't get stuck in analysis paralysis trying to prototype things in an overly complex type system. At the same time you don't necessarily have to throw the types away. Type systems do help and absolutely have certain benefits, etc. And Clojure has a pretty nice one called Clojure.Spec. In Spec you can for example conform that your function returns specific shape of data. You can use specs to validate inputs, produce human readable error-messages, generate fake data and use it for example for property-based testing. Specs can be shared between different systems, between front-end and back-end, etc.
The examples in TS are very easy to understand where Reason just looks like magic and I have to think more about what it might be doing. I don’t see a benefit to Reason over vanilla JS.
/opinion
I'm not a Reason pro, but it doesn't look magical to me. It seems just as explicit, and actually more so in the reducer example.
Typescript is missing the Variant type primitive so it has to be written in a roundabout way to imitate that primitive (and it most likely isn't type checked correctly either). The extra code you are writing in TS is not explicit... a better term is obfuscates.
You mean the restricted type system that allows for principal type inference? ML's type system is much less powerful than Haskell's or Scala's, precisely to allow type inference (e.g. no overloads). The type inference itself isn't very powerful or incredible either, it works just for the exact type system offered by ML.
Obviously nobody likes this, which is why ML offers a number of hacks to allow more powerful type system hackery - e.g. open sums ("polymorphic variants" and also open exception types), HKTs (modules), GADTs, row types & subtyping (objects). Although IMO it would be better to incorporate all these hacks in the core language itself, and only run type inference on the bits where it's possible (but of course tradeoffs would be necessary to avoid running into halting problems etc.).
These are not hacks, subtyping and GADT are part of the core language
> ML's type system is much less powerful than Haskell's or Scala's
Yes, but most of the fancy stuff is located in the module language, and it doesn't lack power, it lacks implicitness (until modular implicits would land).
That's arguable. What's not arguable is that every OCaml compiler runs circles around GHC and scalac in terms of compilation speed. If you like not waiting for the compiler, then you should like OCaml.
This nearly ruined the codebases and we ended up converting them all to TS because:
A) tactically, flow's global inference sucked, so many things that devs thought were typed, were not. I get they're trying a do-over and using OCaml to solve that this time around, which I agree is fundamentally a better approach.
B) strategically, explicit type annotations (i.e. at method boundaries) is a _feature_ for dev readability and comprehension and not a burden to be avoided. I get that Flow/Reason _can_ add these type defs, but many new devs see posts like this, think "nice, less chars to type!", and now write a 100k LOC codebase with a pervasive lack of any type defs.
Tldr, I think TS's explicit types at boundaries is actually best practice for programming-in-the-large.
The problem is not the type inference, but tooling. If your IDE can, when you're coding can 'show-type-at-point' and your online github style code review tool shows the type of any expression, argument, ... when you hover over it, you no longer have this problem.
Well thats pretty stupid..
In practice you don't need to provide any types annotations at all at any point in your program and you should still get the same safety benefits.
Every value in your code will automatically have one single type assigned to it based on how it is being used. When the compiler finds contradictions it will let you know about it. On the other hand, Typescript will try to unify the type to some sort of Any, which is probably not what you want.
I can see the argument for it when you have unspellable nested generic types, or when you don't want to write the same thing on both sides of an assignment. Simple type inference like in TS gives you that.
Having really smart type inference makes your compiler slower and more complicated. What's the real-world benefit?
> Typescript will try to unify the type to some sort of Any, which is probably not what you want.
Not really, unless you push the type inference beyond its limits, in which case you should just change your code.
> Why do people care about this? Writing type annotations is not that much work (...)
I personally care about this when I'm prototyping something. Not having to write types means that I can simply write what's on my mind. The benefit of good type inference is that the compiler can still help me highlight any mistakes I made during this process.
Another benefit is that some production code can get very complex. Having to type every single value is just too cumbersome and distracting. It contributes to boilerplate and increases cognitive load, in my opinion.
> Having really smart type inference makes your compiler slower and more complicated.
I would actually disagree with this. First of all, ReasonML's compiler is absurdly fast. No, seriously try it. Sometimes I go and double check the generated JS code just to be sure it actually did anything.
Regarding the "more complicated" part – the only reason why full type inference works is because it is based on a very solid theoretical foundation. It might be somewhat "complicated", but it will never be as complicated as something ad-hoc that needs to account for all inconsistencies that exist in untyped languages like JavaScript.
In my experience with OCaml, I found that knowing the types of my variables reduce the cognitive load of trying to infer the types myself when reading the code, so despite OCaml supporting type inference, I use explicit type annotations almost everywhere.
What I do use type annotations for is debugging. If the compiler finds a type error, in some situations it helps adding type annotations to find where the actual error is.
Types offer inline documentation to developers that come after you. They should make it easier to establish and maintain a mental model.
Obviously, this requires buy-in from all parties, but if the tool itself doesn't encourage buy-in, it is a bug, not a feature.
The first thing to note is that you do interact with types to build a mental model! The editor will show them to you as you move your cursor around the code. It will do that for every single value. Just move the cursor over that `requestContext` argument and voilà, it'll show you the type.
Another important aspect is that you still have to define types. Have a person object with a bunch of fields? You do have to type annotate every single field. Have multiple actions to handle in reducers? You need to tell ReasonML in advance what those are using a variant type.
And finally, if you care about composition/abstraction you should provide interfaces for modules. This requires writing types for all functions explicitly to ensure that you are exposing a correct protocol. This is optional, but it helps both the author of the code and the consumers.
Type-inference is just a nice to have feature for the low-level implementation details.
This is exactly the reason I prefer Reason. :)
If it was just about stripping away types, then there wouldn't be a need for a compiler and the type checker could be a separate program.
At this point almost all of what Typescript compiles (transpiles) that isn't just "type stripping" is some form of downleveling from current ES standards to older ES versions and is almost all entirely optional. There's also nothing stopping you from leaving it all to Babel to do it instead of having Typescript do it, if you wish.
(Though personally, I think Typescript is a lot lighter weight a solution than most Babel presets and tslib (the optional dynamic link version of the inline downlevel helper functions TS emits) is a lot smaller than Babel's equivalents in core-js.)
ReasonML doesn't compile to Asm.js. Most languages that compile to JavaScript also want to interop with JavaScript APIs and other high-level JavaScript code. Other posts in topic discuss this exact difficulty of combining ReasonML with the JS ecosystem.
The further away you go from Javascript, the more performance issues you encounter and the larger your build size becomes.
Of course in theory nothing stops you from having an entire turing-complete VM inside JS, but that's not what you want for real deployments.
It's not, it's a mere syntax rewriter for OCaml. OCaml has default native target, bytecode target, and two JS transpilers.
This is exactly the reason why Typescript took off and none of the other compile-to-JS languages did (other than a brief flash by CoffeeScript). Dart had a very similar problem as Reason - it wasn't Javascript. The interop between Dart and JS libraries was just too much of a pain to deal with, where in Typescript everything just worked.
Since building your own ecosystem that rivals the JS ecosystem for libraries is an extremely difficult task, any candidate to replace JS must have good interop with its libraries to be successful.
The compile-example is misleading. Clean build times don't really matter. Iteration times with "tsc -w" would've likely been well under a second.
The boiler-plate in the redux example could be reduced, but redux itself is extremely boilerplate-heavy when written in this explicit style. It would be interesting to see how the ReasonML typesystem gets in the way of metaprogramming.
Bucklescript can have a clean build of 1.2k files project in under 4 seconds! https://twitter.com/bobzhang1988/status/1160846763747508224
If your project has a thousand source files of any programming language, it's probably time to quit and join a younger company.
Take a look at this: https://marketplace.visualstudio.com/items?itemName=jaredly....
The only problem that I have found is that in some situations it doesn't report errors correctly when adding new dependencies. Refreshing the window or rebuilding the project usually helps.
On the other hand, the compiler feels very solid. It can be annoying in the beginning because of how disciplined the code needs to be, and the error messages can be hard to understand.
After getting used to that, the dev ex is really smooth specially during complicated refactoring, the compiler and the IDE make things almost boring.
While its abstractions make plenty of sense, its distance from JavaScript makes it incredibly unappealing to the average developer.
ReasonML was specifically designed for JavaScript developers. Being JavaScript-friendly is in it's DNA really. I even see ReasonML a language that was specifically designed to work with React.
I can understand why you would feel this way though. I think the main problem is that ReasonML introduces new concepts that simply do not exist in JavaScript. Learning those concepts is hard. But if it wasn't hard, would you be learning anything or just writing JavaScript in a slightly different way?
Are there any specific examples of things that look too "distant" from JavaScript?
I'll finish this by saying that ReasonML will make you a better JavaScript developer. As will probably learning any other language that challenges the way you think.
I wholeheartedly agree! But a lot of people don't want to be challenged by the language when there are already plenty of other challenges in their personal and professional lives.
With TypeScript you don't need to think half as much, and the thing your boss generally cares about (whether the feature shipped) gets done.
That seems a bit dangerous, to be honest. TypeScript has an unsound type system. If you don't think carefully, you might end up shipping runtime type errors.