Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abandon TypeScript? #541

Closed
benlesh opened this issue Oct 15, 2015 · 37 comments
Closed

Abandon TypeScript? #541

benlesh opened this issue Oct 15, 2015 · 37 comments

Comments

@benlesh
Copy link
Member

benlesh commented Oct 15, 2015

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.

@benlesh
Copy link
Member Author

benlesh commented Oct 15, 2015

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.

@staltz
Copy link
Member

staltz commented Oct 15, 2015

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.

@staltz
Copy link
Member

staltz commented Oct 15, 2015

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.

@benlesh
Copy link
Member Author

benlesh commented Oct 15, 2015

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) 😛

@staltz
Copy link
Member

staltz commented Oct 15, 2015

cs6kykc

@mattpodwysocki
Copy link
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...

@RyanCavanaugh
Copy link

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.

@benlesh
Copy link
Member Author

benlesh commented Oct 15, 2015

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. :\

@benlesh
Copy link
Member Author

benlesh commented Oct 15, 2015

@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.

@RyanCavanaugh
Copy link

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

@benlesh
Copy link
Member Author

benlesh commented Oct 15, 2015

@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.

@RyanCavanaugh
Copy link

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.

@benlesh
Copy link
Member Author

benlesh commented Oct 15, 2015

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?

@RyanCavanaugh
Copy link

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.

@BerkeleyTrue
Copy link

+1 for a move to plain ES2015. It is a barrier to entry for a lot of JS developers.

@robwormald
Copy link
Contributor

Thoughts -

I have had my frustrations with TypeScript, but:

  • having Type completion as an end user is amazing in complex libraries like Rx. The fix @Blesh made yesterday that enabled proper typings via npm is really very cool to see in action, and is a huge step forward for how we're consuming it in angular2. ❤️
  • The latest version of TypeScript (1.6.2+) makes big steps forward in terms of removing a lot of the shenanigans when dealing with .d.ts files as a consumer. With the fix in 64cd865, using the distributed typedefs is literally:
npm install @reactivex/rxjs

then

import {Observable} from '@reactivex/rxjs'

then (note the distinct lack of ///ref chicanery)
screen shot 2015-10-16 at 12 01 51 am

This is a massive step forward and I'd like to see RxJS give things a fair shake with this new change.

As far as custom operators - now we've got typings functioning downstream, creating an NgObservable subclass by overriding lift works fine and method types follow down nicely.

Perhaps? an idea would be to create an Observable abstract class with the primary guts, and then I as a framework author could implement and pick and choose the operators i like (and Rx could ship RxObservable with their full-fat implementation).

I'm sure there's a bit more fiddling to be done to get things to 100% pain-free but for my dollars the benefits are much nicer than the negatives.

All that said, I would literally throw a parade if the functionBind syntax landed in TypeScript at any point in the near future, and would ship cakes and champagne to the team on the regular. Just sayin'.

@benlesh
Copy link
Member Author

benlesh commented Oct 16, 2015

I'll join your parade

@mazhlekov
Copy link

+1 for TypeScript. Definitely "Dynamic typing is really not a sane idea for large codebases, a lot of smart developers advocate types for large projects" its true. Entry barrier may be a bit higher but the code is much more self explainatory. I believe many people go straight into the source code when they are learning the library, for me it works way faster than reading the docs. Also developrs can understand the idea and patterns used in the library just by reading the definitions.

@benlesh benlesh closed this as completed Nov 3, 2015
@benlesh
Copy link
Member Author

benlesh commented Nov 3, 2015

I'm fine with staying with TypeScript for now.

@xogeny
Copy link

xogeny commented Jan 20, 2016

+1 for TypeScript

Note that working with npm has gotten a lot better recently. I think you'd be throwing in the towel right when a lot of these issues are getting dealt with.

I don't understand @BerkeleyTrue's comment about this being a barrier to entry. When you publish the npm module, it is just plain old JS, isn't it? I could see how it might people from contributing, but is it really stopping people from using it?

@BerkeleyTrue
Copy link

@xogeny It is what holds me back from contributing to this repo.

It also makes it difficult to read the source. I spent a lot of time in RxJS 3/4's source code because I often found the docs confusing. I've tried a couple of times to read the source here and I have found TypeScript extremely off-putting.

It reminds me of when I first starting coding and there were a good chunk of open source (OS) projects that were written in CoffeeScript. Even though I could have easily started with CoffeeScript, as a beginner I found having it difficult to set up the tooling need to get CoffeeScript running and thankfully stuck with JavaScript.

This meant I couldn't contribute to those projects. I would find alternatives to those libraries that used CoffeeScript as I couldn't easily understand what the libraries were doing. In some instances I would find an online compiler, and read the JavaScript that was spit out which isn't pleasant at all.

With so many new JavaScript tech to coming out every week, I have neither the time nor the interest to learn Typescript and I discourage those who have OS JavaScript projects from doing so as well.

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.

I think we will find this statement will hold true for this project.

@trxcllnt
Copy link
Member

TypeScript is ES2015 with type annotations and generics. We've sorted out most of the tooling and build issues. I have serious doubts that anybody who can't contribute to the project now would have been able to if it were vanilla ES2015.

@xogeny
Copy link

xogeny commented Jan 20, 2016

@trxcllnt Agreed. CoffeeScript is entirely different. TypeScript is following JS much more closely. Arrow functions, imports, classes, destructuring, etc...those are all aligned with JS itself.

@RyanCavanaugh
Copy link

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.

@bholloway
Copy link

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.

@benlesh
Copy link
Member Author

benlesh commented Jan 23, 2016

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.

@scharf
Copy link

scharf commented Feb 24, 2016

@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.

@BerkeleyTrue
Copy link

@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.

@jeffbcross
Copy link
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).

@experimenti
Copy link

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

@ssmartin
Copy link

+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.

@ericwooley
Copy link

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.

@LongLiveCHIEF
Copy link

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.

@benlesh
Copy link
Member Author

benlesh commented Apr 26, 2016

@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.

@LongLiveCHIEF
Copy link

LongLiveCHIEF commented Apr 26, 2016

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.

@slavovojacek
Copy link

+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.

@lock
Copy link

lock bot commented Jun 6, 2018

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.

@lock lock bot locked as resolved and limited conversation to collaborators Jun 6, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests