Skip to content

Abandon TypeScript? #541

Closed
Closed

Description

@benlesh

Because of the modular nature of this library. I'm starting to feel like maybe TypeScript isn't a natural fit for what we're doing.

Why TypeScript to begin with?

If we were to support TypeScript typings, it made sense to "transpile down". There have been other benefits to the project over type as well, but the main thing was for the automatic generation of typings information for our partners on the Angular team, and for the larger community of TypeScript developers that care about high-quality typings information. The idea was that be using TypeScript, it would generate the best .d.ts files for us.

Issues

Frequently we're forced to jump through hoops just to generate proper .d.ts files. For example: We have to add property "stubs" for methods on Observable we wanted to be optional for modularity's sake. That actually causes other issues where we need to monkey with the prototype of ScalarObservable if we want to override those methods, because TypeScript can't handle reconciling non-existent instance properties that could contain functions and prototype "methods", when there's no real distinction in JavaScript between a "method" on a prototype, and a property key on a prototype that has a function in it.

The goal was to have operators that end users could simply import into their own Observable and decorate the prototype thereof. Basically what we're doing in our Rx.ts file, only specific for their own output purposes. Even better, they could just use ES7 function bind. TypeScript makes the former unergonomic and the latter completely impossible (because it doesn't support function bind).

We also have way too many cases where we're forced to cast as <any> in our app just to make things work that should "just work" in JavaScript. I suppose in a lot of cases, we could use an Interface instead, but it's generally not something that is going to help the public API so it's probably not worth doing anything but the <any> cast. Still, each time we're force to do that what we're really doing is working-around TypeScript.

The good of TypeScript

TypeScript has helped us find and avoid bugs. I've seen it. Places were we were passing the wrong type to the wrong place, etc.

TypeScript makes it really unergonomic to create polymorphic functions. This is a very good thing, IMO, because polymorphic functions should be avoided for performance reasons, and for the reasonability of the functions that you're writing.

Having type information in our code to statically analyze will provide us with opportunities to do smarter things with our minification processes in the future this is something I know our friends at Angular are working on (per @jeffbcross). It may also provide some ability to do more granular "tree-shaking" at some point.

Alternatives

  1. Babel/ES6 port our generated ES6 code over to be our src and maintain .d.ts files by hand. This might actually be less work than dealing with TypeScript because we'll only be typing the type information for the operators one time, and we only really have to maintain it for what is our public API.
  2. ES5 plain, old, ES5 with CJS. Their are a lot of sucky things about this approach. One is that if we wanted to support ES6 modules, we would need to do some sort of transpile in what I would normally consider to be "the opposite direction". We'd also be missing a lot of really nice features.

Thoughts?

The timeline for this would be totally indeterminate; However, I'd want to make the move prior to release if we made a move at all.

Activity

  1. benlesh commented on Oct 15, 2015

    @benlesh
    MemberAuthor

    Also, I'll admit that #468 was, at least in part, stemming from the fact that there is some ugliness around our modular operator structure in TypeScript.

  2. staltz commented on Oct 15, 2015

    @staltz
    Member

    I actually think it was a good idea to build it with TypeScript. For the performance argument (avoiding polymorphism) and for the reliability argument (Dynamic typing is really not a sane idea for large codebases, a lot of smart developers advocate types for large projects).

    Still, each time we're force to do that what we're really doing is working-around TypeScript.

    If you just want to get stuff done and get types out of your way, then there isn't anything better than dynamically typed JavaScript. That's the main benefit of dynamic typing: getting out of the developer's way. Types are there to guide the programmer. We shouldn't default to putting <any> whenever "TypeScript complains". Instead we need to stop and actually think what possible types that thing can assume. The benefit will be greater in the long run, and that's what this project is for.

    Babel/ES6 port our generated ES6 code over to be our src and maintain .d.ts files by hand.

    Either way, we'll have to maintain things by hand, be that .d.ts or property stubs in Rx.ts. And I'd say now is quite late to migrate to ES6.

    ES5 plain, old, ES5 with CJS.

    Definitely not this option. This library targets the future, not the past.

  3. staltz commented on Oct 15, 2015

    @staltz
    Member

    PS:

    they could just use ES7 function bind

    ES7 function bind is a Stage 0 feature, we shouldn't make any assumption on its availability.

  4. benlesh commented on Oct 15, 2015

    @benlesh
    MemberAuthor

    ES7 function bind is a Stage 0 feature, we shouldn't make any assumption on its availability.

    SHUT YOUR STINKIN' MOUTH @staltz! (I love that feature) 😛

  5. staltz commented on Oct 15, 2015

    @staltz
    Member

    cs6kykc

  6. mattpodwysocki commented on Oct 15, 2015

    @mattpodwysocki
    Collaborator

    Yeah, I kept away from TypeScript just because it kept the number of contributions lower in terms of contributions because the barrier to entry was a bit higher. But to each their own...

  7. RyanCavanaugh commented on Oct 15, 2015

    @RyanCavanaugh

    We're looking at this and want to make sure we understand the scenarios before making any recommendations.

    For example: We have to add property "stubs" for methods on Observable we wanted to be optional for modularity's sake.

    My initial take on this is that you should be using declaration merging to add things to the Observable type as they "become available" via imports (but more on that later).

    That actually causes other issues where we need to monkey with the prototype of ScalarObservable if we want to override those methods

    This is the same link as before (intended?) so I don't quite have context on what this means

    because TypeScript can't handle reconciling non-existent instance properties that could contain functions and prototype "methods"

    TypeScript doesn't distinguish these two cases, which I would think would make things easier rather than harder.

    The goal was to have operators that end users could simply import into their own Observable and decorate the prototype thereof.

    Several questions here -- "their own Observable" means a subclass of Observable? Or is the intent that end users should be modifying Observable.prototype from user code ("decorate" === mutate, or decorator?).

    Just thinking concretely, in your ideal modular world, user code would look something like this?

    // Core import
    import * as rx from 'rx';
    // Add the operators I need
    import * as forEach from 'rx-forEach';
    rx.Observable.prototype.forEach = forEach;
    
    // Proceed to user code

    Possibly the big missing piece here is the ability augment global types from within a module.

  8. benlesh commented on Oct 15, 2015

    @benlesh
    MemberAuthor

    This is the same link as before (intended?) so I don't quite have context on what this means

    @RyanCavanaugh ... oops. I meant this link

    Several questions here -- "their own Observable" means a subclass of Observable? Or is the intent that end users should be modifying Observable.prototype from user code ("decorate" === mutate, or decorator?).

    Yes, that's part of the original design. Mostly to support dialects that don't have ES7 function bind.

    Just thinking concretely, in your ideal modular world, user code would look something like this?

    Not really. In my ideal world it looks like this:

    import { Observable } from 'rx';
    import map from 'rx-map';
    import filter form 'rx-filter';
    
    const source = Observable.of(1,2,3,4,5,6,7);
    const mapped = source::filter(x => x % 2 === 0)::map(x => x + '!!!');

    ... but unfortunately, we have needs in the non-transpiled world too, in which prototype decoration is what we're stuck with. :\

  9. benlesh commented on Oct 15, 2015

    @benlesh
    MemberAuthor

    @RyanCavanaugh .... One big, near-term issue for the Angular team around Rx, is: How do they include the operators people will need without bloating their byte-count? This is one of the reasons the modular design exists.

    So, in a world where we need type information for those methods for TypeScript users, I need to export a .d.ts file that shows all possible core operators on Observable if they're there.

    Right now I'm just defaulting to adding all "core" operators as stubs to Observable.

  10. RyanCavanaugh commented on Oct 15, 2015

    @RyanCavanaugh

    What does the ideal world, absent the ES7 bind operator, look like?

  11. benlesh commented on Oct 15, 2015

    @benlesh
    MemberAuthor

    @RyanCavanaugh I think I'd like it if the TypeScript compiler didn't complain that I'm adding prototype methods to a subclass when the superclass's instance properties of the same name are undefined. Alternatively, I'd love it if there was a way to tack on type information in the .d.ts for properties that may or may not be there.

  12. RyanCavanaugh commented on Oct 15, 2015

    @RyanCavanaugh

    Alternatively, I'd love it if there was a way to tack on type information in the .d.ts for properties that may or may not be there.

    Either I'm misunderstanding the scenario, or that's already supported:
    https://github.com/RyanCavanaugh/TryRx/blob/master/sample.ts
    https://github.com/RyanCavanaugh/TryRx/blob/master/augmentations.d.ts

    The one piece that would make the whole story work 100% smoothly is if importing a module could cause side effects to types in the global namespace. In that setup, using the example above, you'd have e.g. a module even that had a type-system side effect of augmenting the global type Observable That's not currently alowed, but it's something we've discussed.

  13. benlesh commented on Oct 15, 2015

    @benlesh
    MemberAuthor

    I guess, I'm too new to this... is augmentations.d.ts some idiomatic way of altering the type information? How does the type system know to pick that up?

  14. RyanCavanaugh commented on Oct 15, 2015

    @RyanCavanaugh

    The filename there is arbitrary - any file you add to your compilation that declares the same interface or namespace in the same scope adds to that type rather than declaring a new one. You'd still need to /// <reference it or otherwise add it to the set of files the compiler sees.

  15. 10 remaining items

  16. RyanCavanaugh commented on Jan 20, 2016

    @RyanCavanaugh

    Granted I'm biased, but I honestly find the notion that having type annotations on code raises the barrier to entry to be completely baffling.

    If I see this method signature (from the actual code, with type annotations removed):

      do(next, error, complete) {
        /** implementation here **/
      }

    I just have no idea what's going on. Is next a callback? How many parameters does it take? Is 'error' the result of some other thing that was thrown? I have to assume complete is a boolean, based on no other cues. I would guess that all parameters are required. What members does next have? What members does this have? Who calls this function? Is it intended to be public, or private?

    When I see this:

      do(next: (value: T) => void, error?: (err: any) => void, complete?: () => void): any {

    Oh. Now I know that next is a void-returning callback that I should invoke with the containing type's type parameter. error is optional, and it's a function that I should invoke with an error object. complete is also optional, but doesn't take any parameters. If I'm not sure what T is, I can Go To Definition to see where it's defined. If I try to pass an incorrect value to next, I'll get an error that tells me what I messed up.

    The hard part about coming to a new codebase is figuring out what the heck is going on. Types drastically simplify that navigation, and serve as a reliable computer-verifiable mechanism for programmers to communicate the semantics of a component.

    As Vonnegut wrote, "any scientist who couldn't explain to an eight-year-old what he was doing was a charlatan". If someone can't write a type annotation to indicate the intent of their code for the next programmer to come along, they shouldn't be writing that code in the first place.

  17. bholloway commented on Jan 21, 2016

    @bholloway

    There are a few reactive libraries and frameworks out there. I don't think I would have looked here if not for Angular 2.0.

    So long as they retain a commitment to this library I think you gota go with whatever they're pushing, and last I heard that's TypeScript.

    I'm not up to speed with TypeScript and cannot really contribute right now either. Sometimes I have to stare at the code a little longer than normal to understand it.

    I never got on the CoffeeScript train either. But if Angular 2.0 is "best with Typescript" then myself and a boat load of other people are going to start learning it on that basis alone.

  18. benlesh commented on Jan 23, 2016

    @benlesh
    MemberAuthor

    So long as they retain a commitment to this library I think you gota go with whatever they're pushing, and last I heard that's TypeScript.

    I love the folks on the Angular team, but I would never, ever choose a programming language based on this. TypeScript was chosen for a few reasons:

    1. To help with refactoring
    2. To help identify and avoid overly-polymorphic function signatures
    3. To automatically generate .tsd files
    4. To catch problems were we're passing the wrong types into functions at build time
    5. It's just ES6with type annotations.

    Number 4 there is key. If it weren't a superset or ES6, would have never used it.

    I'm not up to speed with TypeScript and cannot really contribute right now either. Sometimes I have to stare at the code a little longer than normal to understand it.

    It's just ES6 (aka ES2015). with some extra stuff after a colon.

    TypeScript:

    let x = (a: number, b: number) => a + b;
    
    function y(a: number, b: number): number {
      return a + b;
    }

    ES6:

    let x = (a, b) => a + b;
    
    function y(a, b) {
      return a + b;
    }

    TypeScript:

    class Point {
      constructor(private x: number, private y: number) {
      }
    }

    ES6:

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    }

    Other than that, there's generics for typing purposes, which exist in many other languages (e.g. Foo<T>). And there are interfaces, which are just contracts you expect some object to adhere to. It's really not that different from ES6 at all.

    It can be more typing for sure (heh, pun). But editors like Visual Studio Code, WebStorm, or even Sublime or Atom (with some plugins) make working with TypeScript fantastic when it comes to code completion and refactoring.

  19. scharf commented on Feb 24, 2016

    @scharf

    @BerkeleyTrue I think there is a huge difference between CoffeeScript and TypeScript: CoffeeScript tries to change the syntax of JavaScript little additional benefit. You can essentially automatically convert CoffeeScript -> JavaScript -> CoffeeScript.

    TypeScript on the other hand, adds a lot of information that help tools (IDEs) and humans to improve the correctness of code. It is a superset of JavaScript and that makes it so attractive.

  20. BerkeleyTrue commented on Feb 24, 2016

    @BerkeleyTrue

    @scharf My point was not TypeScript === CoffeeScript. It's that adding an extra layer to this project will reduce the number of developers contributing to this repo. Not everyone who does JavaScript will be able to contribute here because they are not using TypeScript.

  21. jeffbcross commented on Feb 25, 2016

    @jeffbcross
    Contributor

    I love the folks on the Angular team, but I would never, ever choose a
    programming language based on this.

    @Blesh whatever yes you would.
    On Wed, Feb 24, 2016 at 3:59 PM Berkeley Martinez notifications@github.com
    wrote:

    @scharf https://github.com/scharf My point was not TypeScript ===
    CoffeeScript. It's that adding an extra layer to this project will reduce
    the number of developers contributing to this repo. Not everyone who does
    JavaScript will be able to contribute here because they are not using
    TypeScript.

    —
    Reply to this email directly or view it on GitHub
    #541 (comment).

  22. experimenti commented on Mar 1, 2016

    @experimenti

    Please support if possible... Angular 2 + TypeScript + RxJS is an excellent combo.

  23. ssmartin commented on Mar 22, 2016

    @ssmartin

    +1 for Typescript.
    The fact that supports "Clean code" standards (code readability) and type checking (at compile time) are definitely a huge plus from my point of view.

  24. ericwooley commented on Mar 23, 2016

    @ericwooley

    I don't contribute to RxJS, but this came up on top of google when I was searching, so I wanted to throw my two cents in on this conversation and suggest discussing flow.

    I use typescript and flow for two similar projects, and I find that flow has all the benefits (sans it being a little slower to check types) with a lot less headache, because it s still javascript, and it's not trying to be anything else.

    I am an outsider, so feel free to disregard, I won't be offended.

  25. LongLiveCHIEF commented on Apr 26, 2016

    @LongLiveCHIEF

    For anyone who has mentioned barrier to entry... the pain is real. Many people coming onto reactive programming are struggling to learn these new concepts, while at the same time having to grapple with learning how to use babel in order to follow the instructions, and then again having to learn typescript to find out if the problems they're experiencing are bugs in rxjs5, or just problems with how they're trying to implement things.

    Using Typescript is just the straw that broke the camel's back, IMHO. I've got team members right now trying to get up to speed with all of the below, at the same time:

    • es6 modules
    • how to write in node.js using es6 modules (babel)
    • caveats in using babel and writing es6 commonJS libs that will be consumed
    • new async approaches (Observables, generators, iterators)
    • designing/deciphering code using new function binding syntax rules (let, =>, ::)

    Now toss in the additional syntax and et al... that TypeScript is introducing... and you're basically killing any momentum these developers could hope to make.

    Why would these devs just be learning this stuff now?

    For developers who aren't freelancers, or belong to small companies or app factory shops, the "upgrade barometer" for when they need to learn something, at least in the js world, is native support in node.js.

    Many of the es6 features are only now becoming standard in node v4, v5. Many of the features rxjs5 depends on to get up and running easily, require a ton of polyfilling. With the uphill battle of learning all the rest of this stuff at the same time, just groking the polyfilling implications is a barrier to entry.

    By using TypeScript, you're not really giving users the option... they have two choices:

    • get on board with all the polyfilling (barrier to entry/learning curve)
    • live with the struggle of trying to reverse engineer docs/specs written in TypeScript, and using future features not available into node (barrier to entry/learning curve)

    If you would use just plain old es6/es2015, I think you'd allow for one option that is at least on pace with the 99% of the js community that doesn't involve a barrier to entry they haven't already accepted as a sunk cost.

  26. benlesh commented on Apr 26, 2016

    @benlesh
    MemberAuthor

    @LongLiveCHIEF you don't have to use TypeScript to use RxJS 5. Not at all. This entire thread was about using TypeScript to build RxJS 5. You could use plain old ES5 JavaScript, ES6, TypeScript, CoffeeScript, ClojureScript, PureScript, basically anything you want to consume RxJS 5... just so long as it's either JavaScript or it compiles to JavaScript.

    Many of the features rxjs5 depends on to get up and running easily, require a ton of polyfilling.

    There aren't any features that RxJS 5 "depends" on that require polyfilling.

  27. LongLiveCHIEF commented on Apr 26, 2016

    @LongLiveCHIEF

    I know you don't have to use TS to use RxJS 5, and assuming you have your tcs system/tooling all set up, it's very easy to follow the examples set forth by the test specs, to help figure things out that reading the docs might not have helped with.

    If you haven't yet taken the TS leap though, and you're trying out the RxJS concepts to determine if you want to use it in a distributable module, which is a common use case for many, then trying to get RxJS 5 to run using natively supported syntax for node v5, is a bit difficult to figure out.

    case in point... I can't even tell if I'm doing something wrong here, or if I'm running into a bug.... but something that would have taken me 3 minutes 6 months ago (rx, commonjs style requires), has had me scratching my head for hours now.... and either way, I know it's something stupidly simple that just seems to be eluding me.

    I can't help but feel that if the same files I reference in that post were written in .js, I would be able to figure out what I was doing wrong without asking embarrassing questions on SO, or having to take half a day to make sure I fully understand TS and the implications of es6 module that I had all wrong thanks to leaning on how babel v5 used to handle consuming them from require.

  28. slavovojacek commented on Oct 10, 2016

    @slavovojacek

    +1 for sticking with TypeScript.

    Today we are having JavaScript do everything. Soon someone will come up with a JavaScript framework to replicate an operating system. I don't personally think that the language is advanced enough (at least not yet) to support such pressure.

    TypeScript has been instrumental in reducing a specific subclass of bugs and while it is not TypeScript itself that helps reduce business logic bugs (henceforth application bugs), I do believe it makes working with projects running JavaScript more productive. It's very expressive and makes you and/or your team focus on what matters – application logic.

    PS If one's aim is to reduce application bugs, one shall practice TDD.

  29. lock commented on Jun 6, 2018

    @lock

    This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

  30. locked as resolved and limited conversation to collaborators on Jun 6, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions