
Union Types in Flow and Reason - z1mm32m4n
https://blog.jez.io/union-types-flow-reason/
======
esprehn
The TypeScript comment is wrong, the author needs to use a const enum:
[https://www.typescriptlang.org/docs/handbook/enums.html#cons...](https://www.typescriptlang.org/docs/handbook/enums.html#const-
enums)

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%20Scre...](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)

~~~
z1mm32m4n
Ah! 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
FYI 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%20Scre...](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)

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.

------
simplify
ReasonML 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-i...](https://medium.com/@javierwchavarri/performance-of-records-in-
bucklescript-ecf6566fff5c)

~~~
Can_Not
Would love to see a guide on using bucklescript with VueJS.

------
edjroot
As 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.

~~~
z1mm32m4n
Considering 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/))

~~~
fourthark
A 1228 page introduction to the language? Wow.

~~~
djur
It is definitely much more than an introduction. There's a lot of examples and
exercises, and it goes into a lot of detail on some topics. The chapter about
parser combinators is 70 pages by itself. It's only an "introduction" in the
sense that it starts off assuming no Haskell experience.

In a sense, it feels like the textbook for a course combined with transcripts
of the lectures... although it flows better than that, of course.

------
orf
How 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
I believe this is because it takes an enum, not an integer. Perhaps Reason is
communicating this to uglify?

~~~
alangpierce
My 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).

~~~
djur
Your 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.

------
tel
Nitpicky 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
This 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/t...](https://www.typescriptlang.org/docs/handbook/release-
notes/typescript-2-8.html) 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] };

~~~
tel
You can encode sums using unions as you note, but you need to do this in order
to have good abstraction. For instance, an option type `type Option<T> = null
| T` has the weird property of being idempotent `Option<Option<T>> ==
Option<T>` which means that operations working generically over `Option`
cannot exploit free theorems over T.

Mapped and conditional types add power to Typescript's repertoire, surely. On
the other hand, this isn't a deficiency of _row_ types but instead the total
position of Purescript's type system as opposed to Typescript. I just posit
that row types are a nicer formulation for handling records than
union/intersection atop sets of key mappings.

~~~
spion
Depends on what you mean by "good abstraction". If I wish to use sum types to
represent discriminated unions and parametric polymorphic types then yes its
better to make sure the type "sets" don't intersect.

I wouldn't really consider this encoding special though - its standard
practice e.g see Redux. The usage syntax could be better, but its very much a
native, commonly accepted practice that works well.

If however I want to use unions for other things, like simple unions of
`string | number` I can do that too. For example, I can also describe
overlapping records of "options" where specifying one option means you have to
specify another, and I can model that without nesting the option subsets. I
can't do that with sum types.

Its unfortunate but lots of JS libraries do mix parametric types with their
parameters e.g. `T | Promise<T>` means that Promise<Promise<T>> is the same as
Promise<T>, however at least we can model that in the type system.

TypeScript is worse at all things involving parametric polymorphism, thats
true. Mainly due to lack of higher kinded types, but also due to the bad
modeling habits of parametric things built into the JS language (not just
promises - look at array concat). But thats why I qualified my statement with
"if you look at sum types in isolation"

With records, though, its just way ahead - so much so that I'm bothered by the
primitive built in records in most other languages. Its great that row
polymorphism is a nicer way to handle records, but I need the power too, and
TS gives me that.

~~~
tel
I'm not arguing that unions aren't a good choice for Typescript with its given
aim to stay close to normal JS semantics. These choices make it difficult to
abstract in raw JS as well (see all the Promises-aren't-monads noise).

And yeah, type-level programming is super powerful. You could install it into
PS if you wanted, but I don't think that's the goal there.

------
djur
The 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-we...](https://leanpub.com/purescript/read#leanpub-auto-polyglot-web-
programming)) 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.

------
jmhain
From 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
Haxe 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)

------
z3t4
Meanwhile in dynamic land:

    
    
      if(cancelButton == undefined) throw new Error("Specify if there should be a cancel button or not")

------
lnrdgmz
If the Flow type checker ensures exhaustiveness, why does the switch statement
require a default case and an `impossible` function?

~~~
icebraining
Read 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
Miserable hack.

------
z3t4
I 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.

------
yawaramin
This 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
        };

------
FeepingCreature
Are union types the new lambdas?

~~~
jstimpfle
They 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
For 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
I wonder how the dart to js compiler would do

