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

Intersection types #1256

Closed
fdecampredon opened this Issue Nov 24, 2014 · 35 comments

Comments

Projects
None yet
@fdecampredon

fdecampredon commented Nov 24, 2014

Support intersection types ala flowtype

@danquirk

This comment has been minimized.

Show comment
Hide comment
@danquirk

danquirk Nov 24, 2014

Member

We talked about these during the process of designing unions but didn't prioritize them highly as we didn't see a ton of compelling use cases. If you have some examples that would be helpful. It's certainly doable.

Member

danquirk commented Nov 24, 2014

We talked about these during the process of designing unions but didn't prioritize them highly as we didn't see a ton of compelling use cases. If you have some examples that would be helpful. It's certainly doable.

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Nov 25, 2014

For every function that involves mixins :

function mixins<A,B>(base: { new() :A }, b: B}) : { new(): A & B} 

Object.assign<A,B>(a: A, b: B): A & B;
Object.assign<A,B, C>(a: A, b: B, c: C): A & B & C; // few more overload I guess and it's ok
Object.assign<A, B>(a: A, ....B[]): A & B;

fdecampredon commented Nov 25, 2014

For every function that involves mixins :

function mixins<A,B>(base: { new() :A }, b: B}) : { new(): A & B} 

Object.assign<A,B>(a: A, b: B): A & B;
Object.assign<A,B, C>(a: A, b: B, c: C): A & B & C; // few more overload I guess and it's ok
Object.assign<A, B>(a: A, ....B[]): A & B;
@danquirk

This comment has been minimized.

Show comment
Hide comment
@danquirk

danquirk Nov 25, 2014

Member

Mixins are a good example. We talked about them a bit the other day. I believe we need more than intersection types to model them well though, we specifically talked about what the signature for Object.assign would be, it's more like:

Object.assign<T, ...U[]>(target: T, sources: ...U[]): T & ...U[]

unless we manually write out some number of overloads and just never let you mix in more than x things at a time (which is what your overloads there would do). Perhaps that's an acceptable solution though.

Member

danquirk commented Nov 25, 2014

Mixins are a good example. We talked about them a bit the other day. I believe we need more than intersection types to model them well though, we specifically talked about what the signature for Object.assign would be, it's more like:

Object.assign<T, ...U[]>(target: T, sources: ...U[]): T & ...U[]

unless we manually write out some number of overloads and just never let you mix in more than x things at a time (which is what your overloads there would do). Perhaps that's an acceptable solution though.

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Nov 25, 2014

Why could we not do both ? in my example the final overload use rest

fdecampredon commented Nov 25, 2014

Why could we not do both ? in my example the final overload use rest

@danquirk

This comment has been minimized.

Show comment
Hide comment
@danquirk

danquirk Nov 25, 2014

Member

The overloads would hopefully be unnecessary if we had 'rest type parameters' but both could be useful. In any case, mixins are definitely a good example for this, but they're also something we will look at holistically beyond just intersection types given the desired use cases (ex considering what's mentioned in #727 and #311). If there are other examples worth considering specifically for intersection types we should make sure to get those on paper too.

Member

danquirk commented Nov 25, 2014

The overloads would hopefully be unnecessary if we had 'rest type parameters' but both could be useful. In any case, mixins are definitely a good example for this, but they're also something we will look at holistically beyond just intersection types given the desired use cases (ex considering what's mentioned in #727 and #311). If there are other examples worth considering specifically for intersection types we should make sure to get those on paper too.

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Nov 25, 2014

Actually I don't see any other case flow use them to declare function overload, not sure it is really helpful, I guess if we search a bit they could be some special edge case but not that I can think of.

fdecampredon commented Nov 25, 2014

Actually I don't see any other case flow use them to declare function overload, not sure it is really helpful, I guess if we search a bit they could be some special edge case but not that I can think of.

@stanvass

This comment has been minimized.

Show comment
Hide comment
@stanvass

stanvass Nov 26, 2014

I keep finding uses for intersection types in my code (even before I knew flow has them).

A typical example is an injected dependency in a constructor argument that requires an object implementing several interfaces (the combinations are too many to have dedicated interfaces for every combination) for ex.

var x = new Foo(barObject:  Countable & Sortable & Serializable);

I'd really love to see intersection types in TypeScript.

stanvass commented Nov 26, 2014

I keep finding uses for intersection types in my code (even before I knew flow has them).

A typical example is an injected dependency in a constructor argument that requires an object implementing several interfaces (the combinations are too many to have dedicated interfaces for every combination) for ex.

var x = new Foo(barObject:  Countable & Sortable & Serializable);

I'd really love to see intersection types in TypeScript.

@basarat

This comment has been minimized.

Show comment
Hide comment
@basarat

basarat Dec 11, 2014

Contributor

A stackoverflow request for the same : http://stackoverflow.com/q/27325524/390330

Contributor

basarat commented Dec 11, 2014

A stackoverflow request for the same : http://stackoverflow.com/q/27325524/390330

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Dec 16, 2014

Side note, I think it's important to implements intersection type with an algorithm that take in account that order should matter.

interface A {
  a: string;
  c: string;
}

interface B {
  b: string;
  c: number
}

type AB = A & B // { a: string; b: string: c: number };
type BA = B & A // { a: string; b: string: c: string };

I think it's pretty important

fdecampredon commented Dec 16, 2014

Side note, I think it's important to implements intersection type with an algorithm that take in account that order should matter.

interface A {
  a: string;
  c: string;
}

interface B {
  b: string;
  c: number
}

type AB = A & B // { a: string; b: string: c: number };
type BA = B & A // { a: string; b: string: c: string };

I think it's pretty important

@stanvass

This comment has been minimized.

Show comment
Hide comment
@stanvass

stanvass Dec 16, 2014

@fdecampredon Order should matter if one type can override a definition in another.

But another approach could be that types with (incompatible) property collisions can't be intersected (type error).

It's a matter of avoiding accidental errors vs. flexibility. I think the default resolution should be an error for now, with an open possibility in the future to explicitly declare you're ok with the collision (so one should override the other).

stanvass commented Dec 16, 2014

@fdecampredon Order should matter if one type can override a definition in another.

But another approach could be that types with (incompatible) property collisions can't be intersected (type error).

It's a matter of avoiding accidental errors vs. flexibility. I think the default resolution should be an error for now, with an open possibility in the future to explicitly declare you're ok with the collision (so one should override the other).

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon commented Dec 16, 2014

@stanvass yup valid also

@danielearwicker

This comment has been minimized.

Show comment
Hide comment
@danielearwicker

danielearwicker Mar 22, 2015

It's notable that Object.assign, $.extend etc. cannot be usefully described by TS's type system and yet such functions are extremely widely used in real JS code.

TS gives great support to classes. But using classes means following a particular pattern where I have to use this, which can mean a lot of quite radical surgery on thousands of lines of existing JS code. I tried converting a few large modules that way, class-izing them, but it was damn hard work. To avoid that pain as I've adopted TS, I've used $.extend and extra interfaces and casting-I-mean-type-asserting.

interface ICar {
    void accellerate(deltaV: number);
}

interface ISteerable {
    steer(direction: number): void;
}

// Pure intersection of two types! PLEASE DO NOT MODIFY!!
interface ISteerableCar extends ICar, ISteerable { }

So:

var car = carFactory.makeCar();

var s: ISteerable = {
    steer(direction) { ... }
};

var steerableCar = <ISteerableCar>$.extend(car, s);

This pattern is type-safe against changes to both the ICar and ISteerable interfaces: the s object literal will fail to compile if I don't keep it up-to-date with changes to ISteerable.

But it is not safe against independent changes to ISteerableCar. The type assertion in front of $.extend is based purely on trust that future maintainers will read the comment on ISteerableCar and not add methods or further base interfaces.

Classes don't have this problem:

class Car {
    accellerate(deltaV: number) { ... }
}

class SteerableCar extends Car {
    steer(direction: number) { ... }
}

In JS classes are just syntactic sugar: they don't do anything you couldn't do another way. But in TS classes are magic. They conceal a call to an extend helper that is strongly typed, which we cannot do ourselves in TS.

With intersection types I wouldn't need ISteerableCar with a scare-comment. I'd just say ISteerable&ICar. And with a properly typed Object.assign:

var sc: ISteerable&ICar = Object.assign(carFactory.makeCar(), {
    steer(direction: number) { ... }
});

The great thing about JS is that by providing the tools needed to build objects dynamically, it allows library authors to come up with their own solutions to mixins, multiple-inheritance, aspect-oriented cross-cutting concerns and so on. So I'd love to see TS striving to be the compile-time equivalent of that.

This means that the logical building blocks of type relationships (such as intersection) should be regarded as important in-and-of themselves. They allow the type system to express things that can be done at runtime, and so they support library authors who experiment with ways of building objects.

Yes, classes are the ready-to-wear approach. But give library authors the building blocks so they can try other approaches. This is JS's strength - it's not frozen into one classical OO-style. But at the moment TS is rather class-biased. And lots of JS experts love to hate classes...

danielearwicker commented Mar 22, 2015

It's notable that Object.assign, $.extend etc. cannot be usefully described by TS's type system and yet such functions are extremely widely used in real JS code.

TS gives great support to classes. But using classes means following a particular pattern where I have to use this, which can mean a lot of quite radical surgery on thousands of lines of existing JS code. I tried converting a few large modules that way, class-izing them, but it was damn hard work. To avoid that pain as I've adopted TS, I've used $.extend and extra interfaces and casting-I-mean-type-asserting.

interface ICar {
    void accellerate(deltaV: number);
}

interface ISteerable {
    steer(direction: number): void;
}

// Pure intersection of two types! PLEASE DO NOT MODIFY!!
interface ISteerableCar extends ICar, ISteerable { }

So:

var car = carFactory.makeCar();

var s: ISteerable = {
    steer(direction) { ... }
};

var steerableCar = <ISteerableCar>$.extend(car, s);

This pattern is type-safe against changes to both the ICar and ISteerable interfaces: the s object literal will fail to compile if I don't keep it up-to-date with changes to ISteerable.

But it is not safe against independent changes to ISteerableCar. The type assertion in front of $.extend is based purely on trust that future maintainers will read the comment on ISteerableCar and not add methods or further base interfaces.

Classes don't have this problem:

class Car {
    accellerate(deltaV: number) { ... }
}

class SteerableCar extends Car {
    steer(direction: number) { ... }
}

In JS classes are just syntactic sugar: they don't do anything you couldn't do another way. But in TS classes are magic. They conceal a call to an extend helper that is strongly typed, which we cannot do ourselves in TS.

With intersection types I wouldn't need ISteerableCar with a scare-comment. I'd just say ISteerable&ICar. And with a properly typed Object.assign:

var sc: ISteerable&ICar = Object.assign(carFactory.makeCar(), {
    steer(direction: number) { ... }
});

The great thing about JS is that by providing the tools needed to build objects dynamically, it allows library authors to come up with their own solutions to mixins, multiple-inheritance, aspect-oriented cross-cutting concerns and so on. So I'd love to see TS striving to be the compile-time equivalent of that.

This means that the logical building blocks of type relationships (such as intersection) should be regarded as important in-and-of themselves. They allow the type system to express things that can be done at runtime, and so they support library authors who experiment with ways of building objects.

Yes, classes are the ready-to-wear approach. But give library authors the building blocks so they can try other approaches. This is JS's strength - it's not frozen into one classical OO-style. But at the moment TS is rather class-biased. And lots of JS experts love to hate classes...

@NoelAbrahams

This comment has been minimized.

Show comment
Hide comment
@NoelAbrahams

NoelAbrahams Mar 22, 2015

And lots of JS experts love to hate classes..

Where's the evidence for that? Perhaps fairer to say "Lots of small (or legacy) library developers"?

For large-scale application development a class-based approach is the only way. In such a framework, object literals represent the data (while classes represent the business logic). It doesn't pay (in terms of memory, performance and maintainability) to dynamically endow object literals with sophisticated logic as proposed in the example above.

(NB: I don't have any objection to implementing this feature: I only point out that there are good reasons for TypeScript to remain biased towards classes.)

NoelAbrahams commented Mar 22, 2015

And lots of JS experts love to hate classes..

Where's the evidence for that? Perhaps fairer to say "Lots of small (or legacy) library developers"?

For large-scale application development a class-based approach is the only way. In such a framework, object literals represent the data (while classes represent the business logic). It doesn't pay (in terms of memory, performance and maintainability) to dynamically endow object literals with sophisticated logic as proposed in the example above.

(NB: I don't have any objection to implementing this feature: I only point out that there are good reasons for TypeScript to remain biased towards classes.)

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Mar 22, 2015

For large-scale application development a class-based approach is the only way. In such a framework, object literals represent the data (while classes represent the business logic). It doesn't pay (in terms of memory, performance and maintainability) to dynamically endow object literals with sophisticated logic as proposed in the example above.

Please don't expose your point of view as the only source of truth, a lot of javascript programmers hate class, and try to avoid them.
Also I don't know if you think about the typescript compiler itself as a large-scale application but one of the coding guideline of the project is :

Classes

For consistency, do not use classes in the core compiler pipeline. Use function closures instead.

fdecampredon commented Mar 22, 2015

For large-scale application development a class-based approach is the only way. In such a framework, object literals represent the data (while classes represent the business logic). It doesn't pay (in terms of memory, performance and maintainability) to dynamically endow object literals with sophisticated logic as proposed in the example above.

Please don't expose your point of view as the only source of truth, a lot of javascript programmers hate class, and try to avoid them.
Also I don't know if you think about the typescript compiler itself as a large-scale application but one of the coding guideline of the project is :

Classes

For consistency, do not use classes in the core compiler pipeline. Use function closures instead.
@danielearwicker

This comment has been minimized.

Show comment
Hide comment
@danielearwicker

danielearwicker Mar 22, 2015

@fdecampredon 👍

Where's the evidence for that?

The obvious, most influential example would be Douglas Crockford.

danielearwicker commented Mar 22, 2015

@fdecampredon 👍

Where's the evidence for that?

The obvious, most influential example would be Douglas Crockford.

@NoelAbrahams

This comment has been minimized.

Show comment
Hide comment
@NoelAbrahams

NoelAbrahams Mar 22, 2015

(

Please don't expose your point of view as the only source of truth

Please do attempt to keep the discussion civilised as I do not know you from Adam. Thanks! 😃

)

a lot of javascript programmers hate class, and try to avoid them

Again, where is the evidence? What sort of code are they working on? My contention is they are probably small libraries.

Also I don't know if you think about the typescript compiler itself as a large-scale application

I don't think that the TypeScript compiler is a large-scale application at all. It is a very special body of code called a "compiler". It does not in any way represent real-world web/mobile/desktop/server-side business applications.

but one of the coding guideline of the project is :
Classes
For consistency, do not use classes in the core compiler pipeline. Use function closures instead.

The reference to the core compiler pipeline seems to suggest this is some special optimisation.
Also note use function closures instead does not mean "use object literals with functions attached to them instead".

@danielearwicker, I do not quite understand what is sited as evidence from JS experts. Some googling suggests that the man referred to is someone associated with legacy JavaScript systems.

NoelAbrahams commented Mar 22, 2015

(

Please don't expose your point of view as the only source of truth

Please do attempt to keep the discussion civilised as I do not know you from Adam. Thanks! 😃

)

a lot of javascript programmers hate class, and try to avoid them

Again, where is the evidence? What sort of code are they working on? My contention is they are probably small libraries.

Also I don't know if you think about the typescript compiler itself as a large-scale application

I don't think that the TypeScript compiler is a large-scale application at all. It is a very special body of code called a "compiler". It does not in any way represent real-world web/mobile/desktop/server-side business applications.

but one of the coding guideline of the project is :
Classes
For consistency, do not use classes in the core compiler pipeline. Use function closures instead.

The reference to the core compiler pipeline seems to suggest this is some special optimisation.
Also note use function closures instead does not mean "use object literals with functions attached to them instead".

@danielearwicker, I do not quite understand what is sited as evidence from JS experts. Some googling suggests that the man referred to is someone associated with legacy JavaScript systems.

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Mar 22, 2015

Please do attempt to keep the discussion civilised as I do not know you from Adam. Thanks!

When you say

For large-scale application development a class-based approach is the only way.

It's your point of view, however you wrote that sentence like it's an absolute and irrevocable truth, It's not the first time I see you writing such comment and so I ask you in a civilized way to not do so.
Now I don't want to go in a stupid debate on functional programming vs oop If you want some information on the subject, google is your friend.

fdecampredon commented Mar 22, 2015

Please do attempt to keep the discussion civilised as I do not know you from Adam. Thanks!

When you say

For large-scale application development a class-based approach is the only way.

It's your point of view, however you wrote that sentence like it's an absolute and irrevocable truth, It's not the first time I see you writing such comment and so I ask you in a civilized way to not do so.
Now I don't want to go in a stupid debate on functional programming vs oop If you want some information on the subject, google is your friend.

@jbrantly

This comment has been minimized.

Show comment
Hide comment
@jbrantly

jbrantly Mar 22, 2015

Perhaps fairer to say "Lots of small (or legacy) library developers"?

It really doesn't matter. Object.assign is clearly a commonly used function and should be modeled in TS. This whole discussion is pointless and off-topic.

jbrantly commented Mar 22, 2015

Perhaps fairer to say "Lots of small (or legacy) library developers"?

It really doesn't matter. Object.assign is clearly a commonly used function and should be modeled in TS. This whole discussion is pointless and off-topic.

@NoelAbrahams

This comment has been minimized.

Show comment
Hide comment
@NoelAbrahams

NoelAbrahams Mar 22, 2015

@fdecampredon,

For large-scale application development a class-based approach is the only way.
It's your point of view, however you wrote that sentence like it's an absolute and irrevocable truth

I am entitled to voice my opinion as an absolute and irrevocable truth. It is up to others to make one see the errors of their ways. I note that you have failed to do that so far. But you have done better in other topics, so I shall remain hopeful.

@jbrantly, yes, off-topic. As I made clear above. But we can't have the tail (small library developers) wagging the dog.

NoelAbrahams commented Mar 22, 2015

@fdecampredon,

For large-scale application development a class-based approach is the only way.
It's your point of view, however you wrote that sentence like it's an absolute and irrevocable truth

I am entitled to voice my opinion as an absolute and irrevocable truth. It is up to others to make one see the errors of their ways. I note that you have failed to do that so far. But you have done better in other topics, so I shall remain hopeful.

@jbrantly, yes, off-topic. As I made clear above. But we can't have the tail (small library developers) wagging the dog.

@AlexGalays

This comment has been minimized.

Show comment
Hide comment
@AlexGalays

AlexGalays Mar 22, 2015

(I wrote several big applications, and no, a class based approach is not the only way. I quite strongly dislike them, especially the JS ones)

AlexGalays commented Mar 22, 2015

(I wrote several big applications, and no, a class based approach is not the only way. I quite strongly dislike them, especially the JS ones)

@RyanCavanaugh

This comment has been minimized.

Show comment
Hide comment
@RyanCavanaugh

RyanCavanaugh Mar 23, 2015

Member

Stay on-topic (intersection types!), please. This isn't the place to discuss JavaScript application architecture or other people's opinions thereof.

Member

RyanCavanaugh commented Mar 23, 2015

Stay on-topic (intersection types!), please. This isn't the place to discuss JavaScript application architecture or other people's opinions thereof.

@mweststrate

This comment has been minimized.

Show comment
Hide comment
@mweststrate

mweststrate Apr 24, 2015

👍 for intersection types.

Very useful for function that augment, enrich, or mix something in their input. I'm developing full time with Typescript and almost daily encounter useful cases for them. I now have to write explicit casts often, just to make sure the types of my data can be inferred.

mweststrate commented Apr 24, 2015

👍 for intersection types.

Very useful for function that augment, enrich, or mix something in their input. I'm developing full time with Typescript and almost daily encounter useful cases for them. I now have to write explicit casts often, just to make sure the types of my data can be inferred.

@Ciantic

This comment has been minimized.

Show comment
Hide comment
@Ciantic

Ciantic May 1, 2015

I think the order should not matter, it should throw error if one tries to intersect incompatible field types.

If one really wants to intersect incompatible types there could also be a field removal/complement like in elm-lang's records. If there is intersect, why couldn't there be removal and other set operations, like include only fields in both types.

Complement example:

interface PersonWithAddress {
  name: string;
  age: number;
  address: string
}
interface Person = PersonWithAddress - { address } // Remove field address
// Person is now interface { name: string; age: number; }

Complement could work as well with other interfaces:

interface A {
  a: string;
  b: number;
}

interface B {
  b: number;
  c: string;
}

interface C = A - B;
// C is now { a: string; }

With complement in place, it's now possible to use intersection both ways without errors:

interface A {
  a: string;
  c: string;
}

interface B {
  b: string;
  c: number
}

type AB = (A - { c }) & B // { a: string; b: string: c: number };
type BA = (B - { c }) & A // { a: string; b: string: c: string };

And it would not cause errors.

P.S. when thinking these as set operations, the word "intersection" is misleading because A intersection B in above example would be empty set! They have nothing in common.

Ciantic commented May 1, 2015

I think the order should not matter, it should throw error if one tries to intersect incompatible field types.

If one really wants to intersect incompatible types there could also be a field removal/complement like in elm-lang's records. If there is intersect, why couldn't there be removal and other set operations, like include only fields in both types.

Complement example:

interface PersonWithAddress {
  name: string;
  age: number;
  address: string
}
interface Person = PersonWithAddress - { address } // Remove field address
// Person is now interface { name: string; age: number; }

Complement could work as well with other interfaces:

interface A {
  a: string;
  b: number;
}

interface B {
  b: number;
  c: string;
}

interface C = A - B;
// C is now { a: string; }

With complement in place, it's now possible to use intersection both ways without errors:

interface A {
  a: string;
  c: string;
}

interface B {
  b: string;
  c: number
}

type AB = (A - { c }) & B // { a: string; b: string: c: number };
type BA = (B - { c }) & A // { a: string; b: string: c: string };

And it would not cause errors.

P.S. when thinking these as set operations, the word "intersection" is misleading because A intersection B in above example would be empty set! They have nothing in common.

@stanvass

This comment has been minimized.

Show comment
Hide comment
@stanvass

stanvass May 3, 2015

@Ciantic It's confusing a bit, but it helps to think about intersection of types, not of their members. An intersection of two types is the union of their members, because the resulting intersection then belongs to both types.

In simpler scalar types intersection has more intuitive results, i.e. the intersection of signed and unsigned byte is -128...127 & 0...255 = 0...127. The resulting values belong to both types.

Also I agree order should not matter, identical properties should merge, incompatible ones should error out. That's the only way to ensure the resulting type is truly an intersection belonging to both types.

stanvass commented May 3, 2015

@Ciantic It's confusing a bit, but it helps to think about intersection of types, not of their members. An intersection of two types is the union of their members, because the resulting intersection then belongs to both types.

In simpler scalar types intersection has more intuitive results, i.e. the intersection of signed and unsigned byte is -128...127 & 0...255 = 0...127. The resulting values belong to both types.

Also I agree order should not matter, identical properties should merge, incompatible ones should error out. That's the only way to ensure the resulting type is truly an intersection belonging to both types.

@duanyao

This comment has been minimized.

Show comment
Hide comment
@duanyao

duanyao Jun 5, 2015

+1 for interception types.

But I'm confused with the semantic of interception of primitive types, e.g. what should string & number mean? Flow type says it is meaningless:

Not all intersection types make sense. For example, no value has type number & string since there is no value that can have both of those types.

However if interception of primitive types is not allowed, some use cases such as "interface members conflict" can't be modeled properly.

duanyao commented Jun 5, 2015

+1 for interception types.

But I'm confused with the semantic of interception of primitive types, e.g. what should string & number mean? Flow type says it is meaningless:

Not all intersection types make sense. For example, no value has type number & string since there is no value that can have both of those types.

However if interception of primitive types is not allowed, some use cases such as "interface members conflict" can't be modeled properly.

@danielearwicker

This comment has been minimized.

Show comment
Hide comment
@danielearwicker

danielearwicker Jun 5, 2015

@duanyao It can be modelled properly - but only where it makes sense. It's just that some types cannot intersect because they involve a contradiction. They disagree about the type of some member, in a way that cannot be reconciled. If we make such a request, the compiler is maximally helpful if it rejects it.

danielearwicker commented Jun 5, 2015

@duanyao It can be modelled properly - but only where it makes sense. It's just that some types cannot intersect because they involve a contradiction. They disagree about the type of some member, in a way that cannot be reconciled. If we make such a request, the compiler is maximally helpful if it rejects it.

@duanyao

This comment has been minimized.

Show comment
Hide comment
@duanyao

duanyao Jun 5, 2015

@danielearwicker Yes, I am aware of the contradiction. But sometimes the contradiction only exists in TS side, not JS side. In these cases we usually use any to bypass TS type checker -- I wonder if there are better options than any.

duanyao commented Jun 5, 2015

@danielearwicker Yes, I am aware of the contradiction. But sometimes the contradiction only exists in TS side, not JS side. In these cases we usually use any to bypass TS type checker -- I wonder if there are better options than any.

@mweststrate

This comment has been minimized.

Show comment
Hide comment
@mweststrate

mweststrate Jun 5, 2015

If conflicting types are allowed, just use union types, which are already
part of typescript? (e.g MyClass | MyOtherClass)

On Fri, Jun 5, 2015 at 11:06 AM, Duan Yao notifications@github.com wrote:

@danielearwicker https://github.com/danielearwicker Yes, I am aware of
the contradiction. But sometimes the contradiction only exists in TS side,
not JS side. In these cases we usually use any to bypass TS type checker
-- I wonder if there are better options than any.


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

mweststrate commented Jun 5, 2015

If conflicting types are allowed, just use union types, which are already
part of typescript? (e.g MyClass | MyOtherClass)

On Fri, Jun 5, 2015 at 11:06 AM, Duan Yao notifications@github.com wrote:

@danielearwicker https://github.com/danielearwicker Yes, I am aware of
the contradiction. But sometimes the contradiction only exists in TS side,
not JS side. In these cases we usually use any to bypass TS type checker
-- I wonder if there are better options than any.


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

@HerringtonDarkholme

This comment has been minimized.

Show comment
Hide comment
@HerringtonDarkholme

HerringtonDarkholme Jun 12, 2015

Contributor

+1 again for this. It is needed not only by today's Object.assign but also by ES7's object spread

Contributor

HerringtonDarkholme commented Jun 12, 2015

+1 again for this. It is needed not only by today's Object.assign but also by ES7's object spread

@DrRataplan

This comment has been minimized.

Show comment
Hide comment
@DrRataplan

DrRataplan Jun 15, 2015

@mweststrate 'cause that would make a type be either MyClass or MyOtherClass, instead of the actual union of the members: {a: string, b: number} & {d: string, b:string[]} => {a:string, b: number|string[], d:string}.

For now, for Object.assign, I am using an overloaded definition in the lines of:

function assign<A>(a: {}, b: A): A;
function assign<A>(a: A, b: A): A;
function assign<A, B>(a: A, b: B): any; // vs A & B

This works, and is kindof better than the any approach proposed by @duanyao, because it does help the TS type checker in the nontrivial cases of A and B being of the same type and cloning an object (Object.assign({}, {a:2});). Still 👍 for type intersections.

DrRataplan commented Jun 15, 2015

@mweststrate 'cause that would make a type be either MyClass or MyOtherClass, instead of the actual union of the members: {a: string, b: number} & {d: string, b:string[]} => {a:string, b: number|string[], d:string}.

For now, for Object.assign, I am using an overloaded definition in the lines of:

function assign<A>(a: {}, b: A): A;
function assign<A>(a: A, b: A): A;
function assign<A, B>(a: A, b: B): any; // vs A & B

This works, and is kindof better than the any approach proposed by @duanyao, because it does help the TS type checker in the nontrivial cases of A and B being of the same type and cloning an object (Object.assign({}, {a:2});). Still 👍 for type intersections.

@duanyao

This comment has been minimized.

Show comment
Hide comment
@duanyao

duanyao Jun 16, 2015

@mweststrate The problem of {a: string, b: number} & {d: string, b:string[]} => {a:string, b: number|string[], d:string} is that {a:string, b: number|string[], d:string} is assignable to none of its super types.

duanyao commented Jun 16, 2015

@mweststrate The problem of {a: string, b: number} & {d: string, b:string[]} => {a:string, b: number|string[], d:string} is that {a:string, b: number|string[], d:string} is assignable to none of its super types.

@mweststrate

This comment has been minimized.

Show comment
Hide comment
@mweststrate

mweststrate Jun 16, 2015

Ah indeed, overlooked both items. Thanks :)

On Tue, Jun 16, 2015 at 3:09 AM, Duan Yao notifications@github.com wrote:

@mweststrate https://github.com/mweststrate The problem of {a: string,
b: number} & {d: string, b:string[]} => {a:string, b: number|string[],
d:string} is that {a:string, b: number|string[], d:string} is assignable
to none of its super types.


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

mweststrate commented Jun 16, 2015

Ah indeed, overlooked both items. Thanks :)

On Tue, Jun 16, 2015 at 3:09 AM, Duan Yao notifications@github.com wrote:

@mweststrate https://github.com/mweststrate The problem of {a: string,
b: number} & {d: string, b:string[]} => {a:string, b: number|string[],
d:string} is that {a:string, b: number|string[], d:string} is assignable
to none of its super types.


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

@jpolo

This comment has been minimized.

Show comment
Hide comment
@jpolo

jpolo Jun 16, 2015

+1
I am looking forward an expressive solution for something like function (assertAPI, assertExtension) { return mix(assertAPI, assertExtension) } and intersection type would fit right in.

jpolo commented Jun 16, 2015

+1
I am looking forward an expressive solution for something like function (assertAPI, assertExtension) { return mix(assertAPI, assertExtension) } and intersection type would fit right in.

@kseo

This comment has been minimized.

Show comment
Hide comment
@kseo

kseo Jun 17, 2015

+1 for intersection types.

In Flow, intersection types are used to mimic function overloading:

/* @flow */
declare var f: ((x: number) => void) & ((x: string) => void);
f('');

In TypeScript, we use union types to support function overloading.

function f(x: number | x: string): void {
...
}

These two functions actually have the same type because TypeScript used union types to encode intersection types of Flow. Intersection types are the dual of union types. When an arrow is distributed over a union, the union changes to an intersection.

(A -> T) & (B -> T)  <:  (A | B) -> T

I think adding intersection types makes the type system more orthogonal as we often use intersection types to encode union types or union types to encode intersection types. In case of function overloading, I think union types look more natural to most programmers. But typing Object.assign with union types seems not trivial. (Or is it possible?)

kseo commented Jun 17, 2015

+1 for intersection types.

In Flow, intersection types are used to mimic function overloading:

/* @flow */
declare var f: ((x: number) => void) & ((x: string) => void);
f('');

In TypeScript, we use union types to support function overloading.

function f(x: number | x: string): void {
...
}

These two functions actually have the same type because TypeScript used union types to encode intersection types of Flow. Intersection types are the dual of union types. When an arrow is distributed over a union, the union changes to an intersection.

(A -> T) & (B -> T)  <:  (A | B) -> T

I think adding intersection types makes the type system more orthogonal as we often use intersection types to encode union types or union types to encode intersection types. In case of function overloading, I think union types look more natural to most programmers. But typing Object.assign with union types seems not trivial. (Or is it possible?)

@HerringtonDarkholme

This comment has been minimized.

Show comment
Hide comment
@HerringtonDarkholme

HerringtonDarkholme Jun 17, 2015

Contributor

@kseo Just a side note, flow's intersection type is problematic. Please refer to facebook/flow#342 and https://github.com/facebook/flow/blob/master/tests/intersection/test.js

For TypeScript, because function is bivariant, there is probably fewer problems with intersection type

Contributor

HerringtonDarkholme commented Jun 17, 2015

@kseo Just a side note, flow's intersection type is problematic. Please refer to facebook/flow#342 and https://github.com/facebook/flow/blob/master/tests/intersection/test.js

For TypeScript, because function is bivariant, there is probably fewer problems with intersection type

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.