An Intersection type? #18

Closed
gvanrossum opened this Issue Oct 16, 2014 · 8 comments

Comments

Projects
None yet
3 participants
@gvanrossum
Member

gvanrossum commented Oct 16, 2014

Sometimes you want to say that an argument must follow two or more different protocols. For example, you might want to state that an argument must be an Iterable and a Container (for example, it could be a Set, a Mapping or a Sequence). It would be nice if this could be spelled like this:

def assertIn(item: T, thing: Intersection[Iterable[T], Container[T]]) -> None:
    if item not in thing:
        # Debug output
        for it in thing:
            print(it)
@gvanrossum

This comment has been minimized.

Show comment
Hide comment
@gvanrossum

gvanrossum Jan 7, 2015

Member

(In README.text, Łukasz suggested to use Any[] instead of Union[] so that All[] can be used as Intersection[]. Feels a bit cute to me.)

Member

gvanrossum commented Jan 7, 2015

(In README.text, Łukasz suggested to use Any[] instead of Union[] so that All[] can be used as Intersection[]. Feels a bit cute to me.)

@ambv

This comment has been minimized.

Show comment
Hide comment
@ambv

ambv Jan 7, 2015

Contributor

Having intersections is important since we don't have structural sub-classing and protocols will likely not be composable with ABCs.

Any/All is not only cute, it's also shorter to type and more obvious to the reader :)

Contributor

ambv commented Jan 7, 2015

Having intersections is important since we don't have structural sub-classing and protocols will likely not be composable with ABCs.

Any/All is not only cute, it's also shorter to type and more obvious to the reader :)

@gvanrossum

This comment has been minimized.

Show comment
Hide comment
@gvanrossum

gvanrossum Jan 7, 2015

Member

Łukasz, can you show a few examples of how Any/All would be used? Your README note was rather cryptic. I agree Intersection is verbose, but it sounds like a minor use case, and I don't want to lose unparameterized Any as the top (or is that bottom? :-) of the type tree/graph.

Member

gvanrossum commented Jan 7, 2015

Łukasz, can you show a few examples of how Any/All would be used? Your README note was rather cryptic. I agree Intersection is verbose, but it sounds like a minor use case, and I don't want to lose unparameterized Any as the top (or is that bottom? :-) of the type tree/graph.

@JukkaL

This comment has been minimized.

Show comment
Hide comment
@JukkaL

JukkaL Jan 8, 2015

Contributor

I think this would be occasionally useful, but clearly this would be a minor feature. I vote for leaving this out for now and waiting until we have seen some real-world use cases that would benefit from intersection types.

Contributor

JukkaL commented Jan 8, 2015

I think this would be occasionally useful, but clearly this would be a minor feature. I vote for leaving this out for now and waiting until we have seen some real-world use cases that would benefit from intersection types.

@gvanrossum

This comment has been minimized.

Show comment
Hide comment
@gvanrossum

gvanrossum Jan 8, 2015

Member

Agreed. Let's skip it for now then.

Member

gvanrossum commented Jan 8, 2015

Agreed. Let's skip it for now then.

@ambv

This comment has been minimized.

Show comment
Hide comment
@ambv

ambv Jan 8, 2015

Contributor

What I proposed is alternative names: Union becomes Any, Intersection becomes All. typevar(values=) could become OneOf, with the bracket syntax, which would be more consistent.

Examples:

AnyStr = OneOf[bytes, str]
def memcache_get(key:AnyStr) -> Optional[AnyStr]:
  ...

OneOrMany = OneOf[AnyStr, List[AnyStr]]
def memcache_set(key:OneOrMany, value:OneOrMany) -> int:
  ...

# but:
def retry(callback:Callable[AnyArgs, Any[int, None]], timeout:Any[int, None]=None, retries=Any[int, None]=None) -> None:
  ...

# equiv:
OptionalInt = Any[int, None]
def retry(callback:Callable[AnyArgs, OptionalInt], timeout:OptionalInt, retries:OptionalInt) -> None:
  ...

Note that All provides a form of structural typing:

# The following absurd function uses retryable.__len__ and retryable.__call__ 
def retry(retryable:All[Sized, Callable]):
  while len(retryable):
    retryable()

Of course, we could provide more sensible ABCs for "file-like" objects, etc.

I deal with cache invalidation for a living, naming things and off-by-one errors are just a hobby.

Contributor

ambv commented Jan 8, 2015

What I proposed is alternative names: Union becomes Any, Intersection becomes All. typevar(values=) could become OneOf, with the bracket syntax, which would be more consistent.

Examples:

AnyStr = OneOf[bytes, str]
def memcache_get(key:AnyStr) -> Optional[AnyStr]:
  ...

OneOrMany = OneOf[AnyStr, List[AnyStr]]
def memcache_set(key:OneOrMany, value:OneOrMany) -> int:
  ...

# but:
def retry(callback:Callable[AnyArgs, Any[int, None]], timeout:Any[int, None]=None, retries=Any[int, None]=None) -> None:
  ...

# equiv:
OptionalInt = Any[int, None]
def retry(callback:Callable[AnyArgs, OptionalInt], timeout:OptionalInt, retries:OptionalInt) -> None:
  ...

Note that All provides a form of structural typing:

# The following absurd function uses retryable.__len__ and retryable.__call__ 
def retry(retryable:All[Sized, Callable]):
  while len(retryable):
    retryable()

Of course, we could provide more sensible ABCs for "file-like" objects, etc.

I deal with cache invalidation for a living, naming things and off-by-one errors are just a hobby.

@gvanrossum

This comment has been minimized.

Show comment
Hide comment
@gvanrossum

gvanrossum Jan 8, 2015

Member

Sorry, I don't like any of that. Can we just drop it?

  • Using Any[t1, t2, ...] instead of Union[t1, t2, ...] would be confusing if we kept plain Any for its current meaning (it's consistent with everything).
  • Using T = OneOf[t1, t2, ...] instead of TypeVar('T', t1, t2, ...) hides the fact that it is a type variable (and all arguments must match). E.g. def foo(a1: AnyStr, a2: AnyStr): is very different from def foo(a1: Union[str, bytes], a2: Union[str, bytes]):.
  • In #18 we already decided not to define an intersection type for now. And in #11 we decided not to define protocols for now. So I think they shouldn't be "smuggled back in" this way.
  • Type variables should not use brackets, they should use parentheses. I've explained this already (#5).
Member

gvanrossum commented Jan 8, 2015

Sorry, I don't like any of that. Can we just drop it?

  • Using Any[t1, t2, ...] instead of Union[t1, t2, ...] would be confusing if we kept plain Any for its current meaning (it's consistent with everything).
  • Using T = OneOf[t1, t2, ...] instead of TypeVar('T', t1, t2, ...) hides the fact that it is a type variable (and all arguments must match). E.g. def foo(a1: AnyStr, a2: AnyStr): is very different from def foo(a1: Union[str, bytes], a2: Union[str, bytes]):.
  • In #18 we already decided not to define an intersection type for now. And in #11 we decided not to define protocols for now. So I think they shouldn't be "smuggled back in" this way.
  • Type variables should not use brackets, they should use parentheses. I've explained this already (#5).
@ambv

This comment has been minimized.

Show comment
Hide comment
@ambv

ambv Jan 14, 2015

Contributor

OK, that covers it. No Intersection for now, names stay as they were.

Contributor

ambv commented Jan 14, 2015

OK, that covers it. No Intersection for now, names stay as they were.

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