Code Monkey home page Code Monkey logo

Comments (23)

johnnyreilly avatar johnnyreilly commented on March 29, 2024

👍

from typescript.

iislucas avatar iislucas commented on March 29, 2024

From #186; an implementation that leverages the existing lookup power of objects may also ber possible/simpler. Here's what I'm thinking of...

type Animal {
  | Dog;
  | Cat;
  | Bird;
  | Monster { scaryness :number, size :number };
}

That would approximately correspond to a JS object that is assumed to have exactly one of the following parameters, i.e. be treated a bit like:

interface Animal {
  kind_ :string;
  Dog ?:void;
  Cat ?:void;
  Bird ?: void;
  Monster ?: { scaryness :number, size :number, b :number }
}

When you define a variable of this type, e.g.

var monster = Monster {scaryness: 3, size: 2};

it can be compiled like so:

var monster = { kind_: 'Monster', Monster: {scaryness: 3, size: 2} };

Then you can match in a more standard functional programming style of syntax like so:

function logAnimal(animal: Animal) {
  case (animal) {
    | Dog => { console.log('Dog (barks!)'); }
    | Monster(m) => { console.log('Monster is ' + m.scaryness + ' scary'); }
    | _ => { console.log(animal.kind_); }
  };
}

var myMonster :Animal = Animal.Monster { scaryness: 100, size: 5 };
var dog :Animal = Animal.Dog;

logAnimal(myMonster); //should ouput 'Monster is 100 scary'
logAnimal(dog);  //should ouput 'Dog (barks!)'

Which would be compiled to JS like so:

function logAnimal(animal) {
  if(animal.kind_ in animal) {
    var caseSelector_ = {
      'Dog': function() { console.log('Dog (barks!)'); },
      'Monster': function(m) {
          console.log('Monster is ' + m.scaryness + ' scary');
        }
    }
    caseSelector_[animal.kind_](animal[animal.kind_]);
  } else {
    // Default
    console.log(animal.kind_);
  }
}

var myMonster = { kind_: 'Monster', Monster: {scaryness: 100, size: 5} };
var dog = { kind_: 'Dog', Dog: null };

logAnimal(myMonster); //should ouput 'Monster is 100 scary'
logAnimal(dog);  //should ouput 'Dog (barks!)'

That seems to provide reasonable balance of conciseness, readability and efficiency.

from typescript.

basarat avatar basarat commented on March 29, 2024

👍

from typescript.

fsoikin avatar fsoikin commented on March 29, 2024

+1

from typescript.

samuelneff avatar samuelneff commented on March 29, 2024

👍

from typescript.

Evgenus avatar Evgenus commented on March 29, 2024

+1

from typescript.

ivogabe avatar ivogabe commented on March 29, 2024

I like the union style that looks like an interface declaration, but inline I'd prefer |:

var value: Promise<number> | number;
// instead of
var value: union { Promise<number>; number; };

from typescript.

coreh avatar coreh commented on March 29, 2024

We could have A | B be just a syntax sugar for union { A; B; }.

from typescript.

rbuckton avatar rbuckton commented on March 29, 2024

It's extremely difficult to properly type the constructor for an ES6 Promise. In ES6, the Promise constructor takes a callback that receives two arguments: a resolve function and a reject function. If the resolution value passed to the resolve function is itself a Promise or a "thenable", the final state of the resolution value will be adopted by the new Promise. This is difficult to properly type in TypeScript today:

declare class Promise<T> {
  constructor(init: (resolve: (value?: T) => void, reject: (reason: any) => void) => void);
  constructor(init: (resolve: (value: Promise<T>) => void, reject: (reason: any) => void) => void);
}

With the above, you can use contextual typing for the resolve callback to pick the correct constructor overload, but not type inference:

// `resolve` is contextually typed
// result: ok
new Promise<number>((resolve: (value: number) => void) => {
  resolve(0);
});

// `resolve` is contextually typed
// result: ok
var p: Promise<number>
new Promise<number>((resolve: (value: Promise<number>) => void) => {
  resolve(p);
});

// `resolve` is inferred from the first constructor
// result: ok
new Promise<number>(resolve => {
  resolve(0);
});

// `resolve` is inferred from the first constructor
// result: 
// error TS2082: Supplied parameters do not match any signature of call target:
//    Could not apply type 'number' to argument 1 which is of type 'Promise<number>'
// error TS2087: Could not select overload for 'call' expression
new Promise<number>(resolve => {
  resolve(p);
});

// `resolve` is inferred from the first constructor
// result: 
// error TS2082: Supplied parameters do not match any signature of call target:
//    Could not apply type 'number' to argument 1 which is of type 'Promise<number>'
// error TS2087: Could not select overload for 'call' expression
var a = true;
new Promise<number>(resolve => {
  if (a) {
    resolve(0);
  }
  else {
    resolve(p);
  }
});

As a result, the only way to make type inference work here is to add another overload that uses an inline interface with multiple call signatures:

declare class Promise<T> {
  constructor(init: (
    resolve: {
      (value?: T): void;
      (value: Promise<T>): void;
    }, 
    reject: (reason: any) => void
  ) => void);
  constructor(init: (resolve: (value?: T) => void, reject: (reason: any) => void) => void);
  constructor(init: (resolve: (value: Promise<T>) => void, reject: (reason: any) => void) => void);
}

However, even with the above, you still cannot mix both contextual typing of resolve with the ability to pass either a T or Promise as the first argument, unless you type T as any. Type Unions would make this simpler and much more reliable:

declare class Promise<T> {
  constructor(init: (resolve: (value?: union { T; Promise<T> }) => void, reject: (value: any) => void);
}

from typescript.

rbuckton avatar rbuckton commented on March 29, 2024

@ivogabe, Generally I think having a single syntax would be easier to digest for users in the long run. There was a reason I settled on the union { A; B; } syntax over using A | B, though I can't recall offhand what it was. I was a bit concerned about syntax ambiguity.

from typescript.

rbuckton avatar rbuckton commented on March 29, 2024

Type Unions only go part of the way to solving issues with proper typing for things like ES6 Promises. Due to the fact that ES6 Promises adopt the state of a "thenable" resolution value (in essence, recursively unwrapping "thenables" until reaching a non-"thenable" or a rejection), you can never have an ES6 Promise with the type: Promise<Promise<T>>. It's generally easy to avoid this in TypeScript if you allow the compiler to infer the generic type when calling then, however since any type could be applied for the generic type in then, its a possible footgun for end users who could type this:

// p is typed as a Promise<Promise<number>>, but is really just a Promise<number>
var p = promise.then<Promise<number>>(() => ...);

I had additionally proposed a new constraint not for generics that could solve this:

// ES6 "thenable"
interface Thenable<T not Thenable> {
  then<TResult not Thenable>(
    onFulfilled?: (value: T) => ThenableResult<TResult>,
    onRejected?: (reason: any) => ThenableResult<TResult>
  ): Thenable<TResult>;
}

// ES6 "thenable" result
interface ThenableResult<T not Thenable> extends union { 
  T;
  Thenable<T>;
}

// ES6 Promise
declare class Promise<T not Thenable> implements Thenable<T> {
  constructor(init: (resolve: (value?: union { T; Thenable<T>; }) => void, reject: (reason: any) => void);
  ...
  then<TResult not Thenable>(
    onFulfilled?: (value: T) => ThenableResult<TResult>, 
    onRejected?: (reason: any) => ThenableResult<TResult>
  ): Promise<TResult>;
  ...
}

Here, T can be any value that does not match the interface of Thenable<{}>, which would disallow Promise<Promise<number>> along with Promise<jQuery.Deferred<number>> etc. It would also help when determining the correct overload when performing type analysis.

from typescript.

acutus avatar acutus commented on March 29, 2024

+1

from typescript.

Thorium avatar Thorium commented on March 29, 2024

+1

from typescript.

robertknight avatar robertknight commented on March 29, 2024

The slides at 1:37:00 in this video on Facebook's Flow type checker shows a little into how union types are handled there: https://www.youtube.com/watch?v=Bse0sYR7oVY#t=4139

The Flow type annotations are very similar to TypeScript so it might be a good reference.

The cool part is that you can write regular JavaScript (if (typeof foo == "type")) inside a function taking a union to figure out what the type of the variable is and then Flow can infer the type within a block, so you have the benefits of pattern matching but without having to introduce new language constructs.

from typescript.

basarat avatar basarat commented on March 29, 2024

@robertknight nice. More direct link : http://youtu.be/Bse0sYR7oVY?t=1h37m7s

from typescript.

Thorium avatar Thorium commented on March 29, 2024

But you can create same function in TS with multiple different type syntaxes?
More like Haskell pattern-matching than Caml-way suggested here?

from typescript.

johnnyreilly avatar johnnyreilly commented on March 29, 2024

Thanks @basarat / @robertknight - nice to see!

from typescript.

fsoikin avatar fsoikin commented on March 29, 2024

@Thorium, if you use function overloads for this purpose, then their number explodes exponentially.
Consider this function:

function f( x: string | number; y: bool | string );

And here's the way to do it with overloads:

function f( x: string; y: bool );
function f( x: number; y: bool );
function f( x: string; y: string; );
function f( x: number; y: string );

Add another parameter with two possible types, and you get yourself 8 overloads. Fourth parameter - and it's 16. And so on.

This is equally true if you pack these parameters in an interface - except now you also get to define 4 (or 8, or 16) different interfaces, one for every possible combination of types.

And keep in mind that it is a very common pattern to have option bags where every parameter can have different types. Take jQueryUI's position helper for example - pay attention to the of and within properties.

from typescript.

robertknight avatar robertknight commented on March 29, 2024

I said above that Flow type annotations were similar to TS. What I should have said is that they are syntactically compatible where the feature sets overlap. Flow also has non-nullable types and unions.

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 29, 2024

Great discussion. We have a "spec preview" up at #805 that incorporates this feedback and puts up a concrete proposal to work from, so let's continue over there.

from typescript.

omidkrad avatar omidkrad commented on March 29, 2024

FYI, Facebook just announced Flow language. It supports Union Types, and here's an excerpt that differentiates it from TypeScript:

Flow’s type checking is opt-in — you do not need to type check all your code at once. However, underlying the design of Flow is the assumption that most JavaScript code is implicitly statically typed; even though types may not appear anywhere in the code, they are in the developer’s mind as a way to reason about the correctness of the code. Flow infers those types automatically wherever possible, which means that it can find type errors without needing any changes to the code at all. On the other hand, some JavaScript code, especially frameworks, make heavy use of reflection that is often hard to reason about statically. For such inherently dynamic code, type checking would be too imprecise, so Flow provides a simple way to explicitly trust such code and move on. This design is validated by our huge JavaScript codebase at Facebook: Most of our code falls in the implicitly statically typed category, where developers can check their code for type errors without having to explicitly annotate that code with types.

This makes Flow fundamentally different than existing JavaScript type systems (such as TypeScript), which make the weaker assumption that most JavaScript code is dynamically typed, and that it is up to the developer to express which code may be amenable to static typing. In general, such a design leads to reduced coverage: Fewer type errors are caught, and tools are less effective. While this is a reasonable choice for some code, in general such a design does not provide as many benefits as it could without significant additional effort. Still, Flow provides a simple way to switch to this weak mode of type checking where desired, which is typically useful when checking existing code.

from typescript.

6ix4our avatar 6ix4our commented on March 29, 2024

TypeScript will soon support Union types, soon (http://blogs.msdn.com/b/typescript/archive/2014/11/18/what-s-new-in-the-typescript-type-system.aspx). However, I do appreciate hearing about Flow. In my experience, the type inference of TypeScript’s design-time and compile-time modes is already powerful enough for my needs. In fact, many beneficial features of TypeScript, such as Interfaces, Generics (with constraints), Modules, and Enumerations, along with first-class VisualStudio support, npm/JAKE/Travis CI support, and acceptance by tool-makers, like Telerik, aren’t things I’m likely to find in Flow for a great deal of time. Your observation about established JS libraries requiring “significant additional effort” to statically type is patently correct. Thankfully, there has already been a monumental, and ongoing effort to do just that, with DefinitelyTyped.

The advantage I see with Flow, however, is that it’s exposing more developers to the syntactical conventions established in TypeScript. Above that, my assessment of Flow is that it’s a less-mature version of TypeScript. We may not get full “nullable” type support, but literally everything else is worth the investment into TypeScript. Besides, I think, given that all primitives are nullable anyway, it’s the responsibility of the coder in JS to make value checking a priority, at runtime, where any ambiguity exists (say by a call from an externally written bit of JS code), and at compile time with tests. This seems to strike an ironic chord with the stated purpose of Flow: trusting the developer to write good code.

In any case, I hope both projects help evolve JavaScript beyond ES6 with some common conventions and improved design-time safety, while simultaneously providing a better experience for JS development overall in the near-term.

From: Omid K. Rad
Sent: ‎Tuesday‎, ‎November‎ ‎18‎, ‎2014 ‎12‎:‎10‎ ‎PM
To: Microsoft/TypeScript
Cc: David Berry

FYI, Facebook just announced TypeFlow language, and it supports Union Types. Here's an excerpt that differentiates it from TypeScript:

Flow’s type checking is opt-in — you do not need to type check all your code at once. However, underlying the design of Flow is the assumption that most JavaScript code is implicitly statically typed; even though types may not appear anywhere in the code, they are in the developer’s mind as a way to reason about the correctness of the code. Flow infers those types automatically wherever possible, which means that it can find type errors without needing any changes to the code at all. On the other hand, some JavaScript code, especially frameworks, make heavy use of reflection that is often hard to reason about statically. For such inherently dynamic code, type checking would be too imprecise, so Flow provides a simple way to explicitly trust such code and move on. This design is validated by our huge JavaScript codebase at Facebook: Most of our code falls in the implicitly statically typed category, where developers can check their code for type errors without having to explicitly annotate that code with types.

This makes Flow fundamentally different than existing JavaScript type systems (such as TypeScript), which make the weaker assumption that most JavaScript code is dynamically typed, and that it is up to the developer to express which code may be amenable to static typing. In general, such a design leads to reduced coverage: Fewer type errors are caught, and tools are less effective. While this is a reasonable choice for some code, in general such a design does not provide as many benefits as it could without significant additional effort. Still, Flow provides a simple way to switch to this weak mode of type checking where desired, which is typically useful when checking existing code.


Reply to this email directly or view it on GitHub.

from typescript.

NoelAbrahams avatar NoelAbrahams commented on March 29, 2024

In my opinion, and speaking from the perspective of web/mobile application development, the idea of catching errors introduced by null is not an easy problem to solve.

The example given in @omidkrad's link is the following:

function length(x) {
  return x.length;  // Compile time error, because null is an argument below
}
var total = length('Hello') + length(null);

In the real world, we are more likely to see

function length(x) {
  return x.length;
}

var request = new XMLHttpRequest();

request.onload = function() {

    var obj = JSON.parse(this.responseText);
    length(obj); // The web service may always send a non-null response, or may not
};

request.open("get", "www.example.com/getFoo", true);
request.send();

The resolution of this problem requires knowledge of the web service's policy.

In short, for web/mobile application development, data enters the system either over the wire or from user interaction. The nullability of this data is not easily predictable.

from typescript.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.