8 ms·
Union Types in Flow and Reason
- simplify 8y agoReasonML is by far my favorite emerging JavaScript tech. Not only is its JS code output small, it's also performant. For immutable updates it generates code faster[1] that Object.assign! :) [1] https://medium.com/@javierwchavarri/performance-of-records-in-bucklescript-ecf6566fff5c https://medium.com/@javierwchavarri/performance-of-records-i...
- Can_Not 8y agoWould love to see a guide on using bucklescript with VueJS.
- orf 8y agoHow is the uglify.js example correct? It replaces 'status !== 2' with '!(status>=2)' > needsCancelButton(2) false Surely it should be: function needsCancelButton(n){ return 2 === n } Also, more generally, uglify.js has no idea about the range of integers that could be passed in so how is a greater than optimization like that ever valid?
- foota 8y agoI believe this is because it takes an enum, not an integer. Perhaps Reason is communicating this to uglify?
- alangpierce 8y agoMy understanding is that Uglify is just a JS to JS transform, and there isn't a way for Reason to supply type information. At least, I don't see anything about it in the README: https://github.com/mishoo/UglifyJS https://github.com/mishoo/UglifyJS Also note that, at least with Flow and TypeScript, type information isn't guaranteed to be correct, so it's a bit dangerous to do type-based optimizations (although tools certainly could try, with the caveat that if your types are wrong, your code may break even more than it otherwise would).
- foota 8y agoI believe reason is a different flavor than typescript and friends, though I could be mistaken.
- djur 8y agoYour second paragraph is important here. Flow, TypeScript, and PureScript all have two-way interop with JavaScript code as a design goal. PureScript also has generating "fast, understandable code" as a design goal. It might be falling short on "fast" here, but it's much more understandable in isolation than the Reason output. This explains pretty much everything the OP notices in the comparison.
- z1mm32m4n 8y agoAh, that's my mistake! The generated Reason output got copy/pasted incorrectly. (It's correct if you click through to the Try Reason link.) In fact, Reason generates the `>=` comparison, and it knows it can do this because it know that a value greater than 2 can never happen. The post should be updated to reflect this in a second!
- esprehn 8y agoThe TypeScript comment is wrong, the author needs to use a const enum: https://www.typescriptlang.org/docs/handbook/enums.html#const-enums https://www.typescriptlang.org/docs/handbook/enums.html#cons... Then it generates code that's better than Flow. It's not as aggressive as Reason for sure, but it's still pretty good. https://www.typescriptlang.org/play/#src=const%20enum%20ScreenType%20%7B%0D%0A%20%20%20%20LoadingScreen%2C%0D%0A%20%20%20%20CodeEntryScreen%2C%0D%0A%20%20%20%20SuccessScreen%2C%0D%0A%20%20%20%20FailureScreen%2C%0D%0A%7D%0D%0A%0D%0Aconst%20impossible%20%3D%20(x%3A%20never)%3A%20never%20%3D%3E%20%7B%0D%0A%20%20throw%20new%20Error('This%20case%20is%20impossible.')%3B%0D%0A%7D%0D%0A%0D%0Aconst%20needsCancelButton%20%3D%20(screen%3A%20ScreenType)%3A%20boolean%20%3D%3E%20%7B%0D%0A%20%20switch%20(screen)%20%7B%0D%0A%20%20%20%20case%20ScreenType.LoadingScreen%3A%0D%0A%20%20%20%20%20%20return%20true%3B%0D%0A%20%20%20%20case%20ScreenType.CodeEntryScreen%3A%0D%0A%20%20%20%20%20%20return%20true%3B%0D%0A%20%20%20%20case%20ScreenType.SuccessScreen%3A%0D%0A%20%20%20%20%20%20return%20false%3B%0D%0A%20%20%7D%0D%0A%20%20return%20impossible(screen)%3B%0D%0A%7D%0D%0A https://www.typescriptlang.org/play/#src=const%20enum%20Scre...
- huy-nguyen 8y agoI hope the author adds this correction to his article because there’s no way to comment on it directly.
- z1mm32m4n 8y agoAh! Thanks for pointing this out, I'll be sure update the post. (I haven't used TypeScript all that extensively, so I didn't even know about `const enum`.)
- pluma 8y agoFYI here's a variant that eliminates the `impossible` function and instead uses an assignment to the same effect: https://www.typescriptlang.org/play/#src=const%20enum%20ScreenType%20%7B%0D%0A%20%20%20%20LoadingScreen%2C%0D%0A%20%20%20%20CodeEntryScreen%2C%0D%0A%20%20%20%20SuccessScreen%2C%0D%0A%20%20%20%20FailureScreen%2C%0D%0A%7D%0D%0A%0D%0Aconst%20needsCancelButton%20%3D%20(screen%3A%20ScreenType)%3A%20boolean%20%3D%3E%20%7B%0D%0A%20%20switch%20(screen)%20%7B%0D%0A%20%20%20%20case%20ScreenType.LoadingScreen%3A%0D%0A%20%20%20%20%20%20return%20true%3B%0D%0A%20%20%20%20case%20ScreenType.CodeEntryScreen%3A%0D%0A%20%20%20%20%20%20return%20true%3B%0D%0A%20%20%20%20case%20ScreenType.SuccessScreen%3A%0D%0A%20%20%20%20%20%20return%20false%3B%0D%0A%20%20%7D%0D%0A%20%20let%20impossible%3A%20never%20%3D%20screen%3B%0D%0A%20%20return%20impossible%3B%0D%0A%7D%0D%0A https://www.typescriptlang.org/play/#src=const%20enum%20Scre... This is what uglify makes of that: var needsCancelButton = function (e) { switch (e) { case 0: case 1: return!0; case 2: return!1 } return e }; EDIT: Just using `return screen` without the reassignment also works because the return type of the function and the type of the argument are incompatible, but that seems a lot more error prone and less widely applicable that explicitly reassigning to a `never` variable and returning that.
- edjroot 8y agoAs someone who 1) knows a few functional programming concepts, 2) hasn't programmed extensively in a functional language, 3) would like to get productive fast (in web development), but also 4) wants a language for the long term (think general purpose, decent momentum), including for academic interests... Is there a language that fits all these requirements, or am I asking too much? Elm is too limited, Haskell seems too convoluted, unsure about F# and OCaml, don't know about good alternatives. I've been considering PureScript because it seems to strike a balance between features, JS interoperability and skill transferability, but I'm still a little afraid of it being unnecessarily complex for my next short-term projects. I've only now started considering ReasonML, but am I wrong to think it's much closer to the "get productive fast" side than the "long-term investment" one? Not to mention its JS-like syntax, which I admit I have a distaste for.
- w4tson 8y agoHow about Kotlin? First class support on Android. Great tooling and compiles to JS
- thardus 8y agoThe purescript example in the post shows one of Purescript’s biggest problems, and that’s non-optimal codegen. The series of if checks (O(n)) it generates as opposed to a switch statement can really add up in loops with bigger enums. Of the langs listed, ReasonML/OCaml have the best codegen/ffi story, it’s relatively easy to drop them into existing JS projects.
- z1mm32m4n 8y agoConsidering you mention not having programmed extensively in a functional language, I'd say you should pick a language with extensive documentation for beginners. I know you dismissed it, but many people have put a lot of time and thought into designing materials for learning Haskell. In particular, I think most people recommend the Haskell book[1]. That being said, all the choices you list (Elm, Haskell, F#, OCaml, PureScript) are solid languages with good communities. The core languages are similar enough that if you start learning one, the things you learn will transfer decently enough to all the others. [1]: Haskell Programming from First Principles (http://haskellbook.com/ http://haskellbook.com/)
- lnrdgmz 8y agoIf the Flow type checker ensures exhaustiveness, why does the switch statement require a default case and an `impossible` function?
- icebraining 8y agoRead the linked previous post[1]. Flow doesn't ensure exhaustiveness unless you ask it to, by telling it that any case that goes to "default" is invalid (impossible). [1] https://blog.jez.io/flow-exhaustiveness/ https://blog.jez.io/flow-exhaustiveness/
- frou_dh 8y agoMiserable hack.
- bgergen 8y agoYou can also just cast screen to empty in the default case by adding a line that says (screen: empty). This is how the flow docs recommend doing it in Redux reducers.
- jmhain 8y agoFrom the section on TypeScript: > enums, which are basically like Java’s enums. They really aren't. Java enums are somewhat unique in that they are full-fledged classes, not just glorified constants. TS enums are much closer to those of C#.
- andyonthewings 8y agoHaxe also produces pretty nice JS output: var needsCancelButton = function(screen) { switch(screen) { case 0:case 1: return true; case 2: return false; } }; The Haxe code can be found here: https://try.haxe.org/#79e40 https://try.haxe.org/#79e40
- djur 8y agoThe difference between Reason's and PureScript's output is easily explained if you take into account the differing goals of the two projects. PureScript has an emphasis on [JS interop](https://leanpub.com/purescript/read#leanpub-auto-polyglot-web-programming https://leanpub.com/purescript/read#leanpub-auto-polyglot-we...) going both ways. Being able to add some PureScript code to a JS codebase, or even to intentionally layer a JS frontend on top of a PureScript core, is an explicit design goal. You can actually create instances of PureScript types in JS and pass them into functions defined in PureScript. That explains why there's a JS class for each data constructor. Similarly, the generated PureScript code can't actually "know[] the match is exhaustive", since it could be called from anywhere. The article was interesting, but I think the comparison would benefit from providing an example of the code generated for calling the function as well as its definition.
- FeepingCreature 8y agoAre union types the new lambdas?
- jstimpfle 8y agoThey seem overused and overpraised. They have their uses, but largely you don't want to mash together different things in a program if you can avoid it. Because it leads to unmaintainable switching code. Alternatives: Have n containers of simple types instead of one container with elements of union types with n alternatives. Sometimes that's not possible because you need to process them in a specific order, but even then you can just keep an additional reference table that references into the n containers. Another alternative would be to make the type an ADT (of course, not always applicable, either).
- icebraining 8y agoFor the Appendix: Idris is also a functional language that can compile to JS. I wouldn't say the output is very readable, but except for a couple of JS interop issues, I haven't had too look at it at all :)
- komuW 8y agoI wonder how the dart to js compiler would do
- z3t4 8y agoMeanwhile in dynamic land: if(cancelButton == undefined) throw new Error("Specify if there should be a cancel button or not")
- z3t4 8y agoI think the root of the problem is overzealous use of OOP patterns like inheritance. If you would do the most naive implementation you would just do one view, copy it, and when needed a fourth, just copy it again, instead of entangling your code with yet more paths.
- tel 8y agoNitpicky but critical detail: union types are not the same as "sum" types such as what you get in Reason, Purescript, and Elm. Union types are convenient for adding atop a dynamic language or modeling records (although, Purescript's row types are better), but they are less capable of abstraction. The key example to differentiate union and sum types is `int union int == int` whereas `int sum int` is something unique and different from `int`. A sum type keeps track of the "left side" versus the "right side". So, all said and done not only is Reason producing faster, tinier code... it's also managing a more sophisticated and powerful abstraction!
- spion 8y agoThis doesn't sound right Sum types are not more capable of an abstraction than union types. If we consider them in isolation, they are strictly less capable. For example, its possible to model sum types using union types in TypeScript by simply adding a tag field: type Either<T, U> = { kind: 'left', left: T } | { kind: 'right', right: U } If you do this, typescript will also be able to do exhaustiveness checks provided its configured with noImplicitAny and strictNullChecks: For example, try removing a case item in the below code (paste it on http://www.typescriptlang.org/play/ http://www.typescriptlang.org/play/ and in options turn on the above mentioned checks): type Stringifiable = { toString(): string }; function stringify<T extends Stringifiable, U extends Stringifiable>(thing: Either<T, U>):string { switch (thing.kind) { case 'left': return 'Left ' + thing.left.toString() case 'right': return 'Right ' + thing.right.toString() } } edit: additionally, PureScript row types are not strictly better. While row polymorphic records are a better tool to model records (they can model open or closed records and subtype relations are not needlessly complex), AFAIK PureScript still lacks advanced transformation tools such as mapped and conditional types which really make TS records shine (see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html https://www.typescriptlang.org/docs/handbook/release-notes/t... for example) Motivating example (given a record type, returns a record type containing only the properties that are not functions): type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]; type NonFunctionProperties<T> = { [P in NonFunctionPropertyNames<T>]: T[P] };
- 8y ago
- yawaramin 8y agoThis is a nice insight into Reason's handling of sum types. Here's the Reason code as we'd typically write it: let needsCancelButton = fun | LoadingScreen | CodeEntryScreen => true | SuccessScreen => false };