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

Intersection types with &? #132

Closed
ericelliott opened this issue Apr 17, 2017 · 16 comments
Closed

Intersection types with &? #132

ericelliott opened this issue Apr 17, 2017 · 16 comments

Comments

@ericelliott
Copy link
Owner

ericelliott commented Apr 17, 2017

I ran into a use case where object spread would not work and the & operator would have been very useful for intersection types (see #18#9). Were there any good reasons not to add it?

@ericelliott
Copy link
Owner Author

I'm experimentally using & in the context where a function mixes props into an existing object, e.g., certain functional mixins:

withFlying = o => m: o & {
  fly () => m,
  land () => m,
  isFlying () => Boolean
}

In this example, m is the result of mixing fly(), land() and isFlying() into the existing object, o.

I like this better than the ... syntax, because the spread syntax implies that a new type is being created, rather than an existing type extended at runtime.

@Mouvedia
Copy link
Collaborator

What you are describing looks like a fluent API to me. Here's how I did it myself: API
I didn't find Rtype concise enough for that use case.

The consequence would be that a letter (as a name) always represent the same thing for the current interface. Can it be a literal?

@ericelliott
Copy link
Owner Author

ericelliott commented Apr 18, 2018

The consequence would be that a letter (as a name) always represent the same thing for the current interface.

The letter m represents the newly mixed o & object. It is valid within the scope of the current interface. Is that what you're referring to?

Can it be a literal?

I'm not sure what you're asking. What is "it"?

@Mouvedia
Copy link
Collaborator

Mouvedia commented Apr 18, 2018

Is that what you're referring to?

I was thinking of using the same letter again for a second signature. Id reckon it would be isolated from the first one.

What is "it"?

Can we have an intersection which takes into account that some types cover literals and objects?
Primitives are auto-wrapped in JS.
e.g. toLowerCase on 'a'.

In your example can the {} in o & {} be the prototype?
If so can m be a literal or an object?

@ericelliott
Copy link
Owner Author

I was thinking of using the same letter again for a second signature. Id reckon it would be isolated from the first one.

For that to work, m would need to be a typeclass (perhaps Flying), and o needs to be a type parameter. We have not documented detailed support for typeclasses, yet:

typeclass WithFlying(o) = m: o & {
  fly () => m,
  land () => m,
  isFlying () => Boolean
}

Then you could reuse it like:

withFlying = WithFlying W ~> o => W(o)

You can read that as "W is the typeclass WithFlying in o => W(o)". o is a type parameter that customizes the typeclass WithFlying to include the o type by type variable substitution in the typeclass definition.

@ericelliott
Copy link
Owner Author

Can we have an intersection which takes into account that some types cover literals and objects?
Primitives are auto-wrapped in JS.

m is not a literal, m is the result of the intersection. o could be a primitive, which in JavaScript, does imply an interface that includes the primitive's prototype. For example, 'my string' implies that its interface has a .trim() method.

@Mouvedia
Copy link
Collaborator

If so can m be a literal or an object?

By be I meant represent obviously. I was just checking if the distinction was required because Rtype doesn't make it.

@ericelliott
Copy link
Owner Author

Rtype is a structural type system. It doesn't care how an interface is satisfied. Only that it is. That means literals automatically imply their full prototype interfaces.

@Mouvedia
Copy link
Collaborator

Mouvedia commented Oct 9, 2018

What you are trying to achieve here is an addition and not really an intersection per se.

I don't like introducing new operators but + would be logical in this case.
What's great about + is that you can go even further and use its pendant - to subtract from an object, which is very useful if the base object is huge.

@ericelliott
Copy link
Owner Author

We're already using an intersection in the docs:

JSON::parse(String, reviver: Function)
  => !Function & !Void & !Symbol,
  throws SyntaxError

// which is equivalent to

JSON::parse(String, reviver: Function)
  => !(Function | Void | Symbol),
  throws SyntaxError

But it isn't described.

Both TypeScript and Flow use & for intersections, and it's idiomatic in type documentation already. I don't think we should mess with it.

@ericelliott
Copy link
Owner Author

ericelliott commented Oct 10, 2018

Flow's intersection docs are a good reflection of what I'm thinking for this syntax, however, on reflection, this should work equally well:

withFlying = o => m: {
  ...o,
  fly () => m,
  land () => m,
  isFlying () => Boolean
}

That said, I still think we should add & just because Flow and TypeScript both have it, and the syntax should be familiar to everybody who's used either.

Does this make any sense?

{ ...!Function,  ...!Void, ...!Symbol }

I don't know if intersections work with negations. Do they?

@Mouvedia
Copy link
Collaborator

I was reluctant to the additions of new operators just for these uncommon use cases. How would you achieve subtraction using ...?

foo() => definedObject - {
 bar: Number,
 qux: Function
}

& ! doesn't work in this case.

Does this make any sense?

If you are talking about the ...!, no it doesn't. But that's just me you should ask around.

@ericelliott
Copy link
Owner Author

Which type notations support the difference (subtraction) or complement operations?

I think anybody familiar with algebra would recognize the subtraction operator as set difference (types and sets are good analogs, IMO).

That said, I think difference deserves it's own issue. ;)

@Mouvedia
Copy link
Collaborator

I think difference deserves it's own issue

I wouldn't introduce - without +.

Which type notations support the difference (subtraction) or complement operations?

check #139

TL;DR: if we introduce +, - would replace !:

JSON::parse(String, reviver: Function)
  => !(Function | Void | Symbol),
//=> !Function & !Void & !Symbol,
  throws SyntaxError

// would become

JSON::parse(String, reviver: Function)
  => Any - (Function & Void & Symbol),
//=> Any - Function - Void - Symbol,
  throws SyntaxError

@ericelliott
Copy link
Owner Author

I think I'll write up a more detailed proposal of set operations and close this issue.

@Mouvedia
Copy link
Collaborator

set operations

You mean append, add, remove?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants