Hacker News new | ask | show | jobs
by fleshweasel 3744 days ago
I strongly disagree about TypeScript-- I think it's a huge boon to productivity. TypeScript has has union types i.e. "number | string" which are similar to algebraic data types. TypeScript also has optional interface members and function parameters by putting ? at the end of the name, i.e. "foo?: number".

Static types allow for much, much better tooling, particularly autocomplete and the ability to check whether your code is valid on some basic levels. I consider avoiding it to be a big waste of time. I've had a good experience getting the definitions files going for the libraries I use.

I also disagree with the statement that TypeScript is making JavaScript "like C# or Java". TypeScript lets you opt out of type checking all you want with minimal difficulty. It also will by default always emit JavaScript even if it detects type errors.

5 comments

> TypeScript has has union types i.e. "number | string" which are similar to algebraic data types.

I understand that you said "similar", but there's actually a big difference that should be mentioned explicitly, namely that algebraic data type (ADT) sums always have "constructors" which you can use to disambiguate with. That means that you can meaningfully do the equivalent of "int | int" whereas for union types that would just be "int". (I'm sure you already know this, I'm just pointing this out for those who may not know or appreciate the subtleties.) Example:

    data Maybe a = Nothing | Just a
In this example the "constructors" are Nothing and Just.

Of course, you can emulate first-order ADT sums using union types by just introducing artifical container classes and doing a union on those. While this works for simple cases, I believe (but cannot prove) that it's impossible to emulate GADTs using union types -- my intuition is that the presence of constructors to match on is an essential part of being able to "narrow" the types sufficiently to actually act upon what they "contain" (for each case).

However, and notwithstanding all of that... the use of union types in TypeScript is absolutely the best way to align with JavaScript since there's so much JS that just takes/returns values of type "whatever" (string | number | ...).

Btw, also agreed on the productivity boost. In the short term, it may not appear that you're getting faster, but once your application starts to grow beyond "trivial" you really start to notice the fact that you can refactor without fear.

Flow has pretty good support for tagged unions like this which act like ADTs:

http://flowtype.org/blog/2015/07/03/Disjoint-Unions.html

This is great. Seems less cumbersome than TypeScript's user defined type guards for distinguishing cases of a union. The sentinel value concept is something you could at least steal when creating user defined type guards to make them simpler, at least.
TypeScript union types and Flow tagged unions are nice to have, but to make them really useful you also have to provide a good way to pattern match on them. And there they both are still way behind Haskell.
That's interesting! I haven't paid much attention to Flow since trying out the initial release and finding it a bit lacking. (But then, I'm used to GHC/Haskell, so everything lacking!)

I truly feel that "disjoint unions" is one of those things you don't appreciate the true value of until you've used them for quite a while. The real eye-opener for me was implementing a state machine and finding that I could explicitly state exactly which bits of the state machine would propagate to $NEXT_STATE at every step... and have the compiler double-check for me that I got it right.

EDIT: I accidentally a accidentally.

It's gotten a lot better since the initial release. Obviously no match for GHC but it's not trying to be, either. :)
Understood :).
This is really nice with a very good much to common JS patterns. Among other things it types the usage of string literals making possible to typecheck and refactor code that contains if (foo === 'bar').
Thanks for elaborating on the distinction. User defined type guards can get you some of the way there to distinguishing "int | int", but you don't get it for free-- the reality of being a superset of JavaScript. https://gist.github.com/RikkiGibson/74fa3dbfdb1b2d7ab86d
Oh, wow, that's pretty evil code! :)
ADTs can be very easily replaced with visitors.

  type Maybe<T> = <V>(v:{just(x:T):V, nothing():V}) => V
  
  let just = <T>(x:T):Maybe<T> => <V>(v:{just(x:T):V}) => v.just(x)
  let nothing = <T>():Maybe<T> => <V>(v:{nothing():V}) => v.nothing()
  
  let values:Maybe<number>[] = [just(6), nothing()]
  
  values.forEach(maybe => {
    alert(maybe({
      just: x => 'got: ' + x,
      nothing: () => 'nothing'
    }))
  })
Easy, but absolutely horrid on a large scale. (I mean, just try to enumerate the amount of redundancy you have in that short snippet you posted!)
Agree.

Typescript doesn't have real algebraic data types becauses at the end of the day, Typescript is just a statically-typed version of Javascript. And Javascript doesn't have adts.

For me, the biggest advantage of static typing is that code is much more self-documenting. Also, refactoring becomes much easier.

Refactoring type safe code vs non type safe code is like night and day. We had a code base in js where even minor refactoring meant frenetic greping and manually replacing things a whole day, fixing problems for a week and occasionally getting a remaining bug on the production site a month after that. We rewrote the whole thing to typescript and after that you could do major refactoring in just minutes and be quite sure that nothing else would break. Also during the rewrite, the compiler always pointed out at least one potential bug in each file, this was quite funny to see because we already considered our old js-code to have quite high quality.

The whole code browsing experience is just different, you can actually read partial sections of the code base and understand them, use code completion, "go to definition" and "find all usages" and it works reliably. Not using types is in my book professional misconduct.

His comparison with C#/Java is also very weird and shows he hasn't given it a proper try. The ducktyping nature of interfaces makes the code much less rigid than in java but still maintains most of the safety you need.

Generally when TypeScript is compared to C#/Java, it is about the syntax look, or when contrasting it to other type system such as, let say, Scala. And then it's very much on the C#/Java side of the fence.

TypeScript definitely got the structural checking of interfaces right, but I beleive (correct me if I'm wrong, I'm referring to https://github.com/Microsoft/TypeScript/issues/202) that classes are also structurally checked. Flow does nominal checking for those, with very good reasons.

While it's a shame the details are light because you kind of need the presenter to go with the slides, the following explains a few of the reasons between TypeScript vs Flow:

http://www.scs.stanford.edu/16wi-cs240h/slides/fb-slides.htm...

(not the whole deck, just a few slides after 45)

For comparison, Flow supports tagged unions like this:

http://flowtype.org/blog/2015/07/03/Disjoint-Unions.html

TS has string types starting from 1.8, although they can't be used for narrowing down the type yet in the way that Flow can.

http://bit.ly/1LhA5id

The article isn't clear on this, but eslint and Babel works together to provide just enough typing to get you by, even checking symbol references across modules.

I was planning in giving TypeScript a go after having some problems a type system should be able to fix, but after having used just eslint for a while I don't really feel the need any more. Maybe something to reduce the syntax tax of Reacts propTypes would be nice, but then I'm set.

After all, ES is a heavily late bound, dynamically dispatched kind of thing. Which means a truly useful static type system supporting things like statically dispatched type classes and such, would force a completely different semantic on the language. I can't imagine that would be fun to debug in a live browser session.

Actually going back to C# now is a frustrating experience compared to ES6 in just how the type system limits you. Granted that has more to do with nominal typing vs structural, then static vs dynamic. I'm guessing TypeScript don't have the same issues.

Flow's way of handling classes nominally and interfaces structurally is really nice.

By the way, can you extrapolate on eslint and babel working to check symbols across modules? I don't think I ever noticed that feature.

Thanks for posting your thoughts. I was leaning towards TypeScript myself (as opposed to ES6) for an Angular project that will be migrated to version 2.0. Angular 2 was actually written in TypeScript if I am not mistaken, and it's one of the three say, "supported" ways of writing A2 apps (along with ES5 and Dart).
Strongly agree. Surprised the author felt this way...