You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
...
x=bool('')
reveal_type(f(x)) # Union[str, bytes] since we don't know the boolean value statically?
We could also have a generic class where a generic type argument depends on the value of an argument to __init__. Not sure how we should represent these -- here is one potential approach:
I think we can have LiteralType as in LiteralType['w'], LiteralType[True] etc. I think it will be better than special-casing True/False/string. Maybe we will get simple constant propagation "falling out" of type aliases (which might be helpful for e.g. NamedTuple)
I think regex will be problematic. How do you determine whether they overlap? String literals seems to suffice.
(And I don't think it counts as dependent type since it has little to do with runtime).
Reacted by Taine Zhao, Nelson Oliveira, Solomon Ucko and Alisa Sireneva
The mode string syntax for open() is more complicated than just 'r', 'w', etc. Hence the regex.
I find LiteralType[True] pretty verbose; also it's more general than we want to implement (we really don't want this to be a big project, just enough to handle the cases @JukkaL mentioned).
If this doesn't count as a dependent type maybe you can explain your definition of dependent types? And what would you call this?
I'm not sure going into definitions is helpful, but the difference is (I guess) the reason you call it "Simple" dependent types - there nothing like def foo(x: int, y: bar[x]) -> str if x == 5 else bool. There's only information immediately available at compile time. Instead of defining True to be a literal of type bool, we define it as being a literal of type TrueType which is a subtype of (or coercible to) bool. I don't see the difference between this and the kind of types we already have. Just because the type has precisely single value you have precise information about its runtime value, but this is already the case with None, Void and Tuple[()].
However, I might be wrong. I am also too easily dragged into issues of terminology.
Possibilities for when you can't statically determine the overload variant
because your argument isn't literal enough:
* Union the alternatives you've got
* Use the return type of the implementation (if any) (simpler, I think)
* Use `Any` (simplest, I think)
On Mon, Mar 27, 2017 at 2:37 PM, Guido van Rossum ***@***.***> wrote:
The mode string syntax for open() is more complicated than just 'r', 'w',
etc. Hence the regex.
I find LiteralType[True] pretty verbose; also it's more general than we
want to implement (we really don't want this to be a big project, just
enough to handle the cases @JukkaL <https://github.com/JukkaL> mentioned).
If this doesn't count as a dependent type maybe you can explain your
definition of dependent types? And what would you call this?
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#3062 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABjs4Q_pmhJYnbeMNorZzX3OAIZpKeCzks5rqCw1gaJpZM4Mqfj6>
.
I suspect special cases will continue to appear and build, and one day we will have to do this big project - which I have the feeling is not big right now, but will be big then and not easily backwards-compatible since special-casing is too easy to over fit for specific applications.
How complex is the mode for open? Correct me if I'm wrong, but it is not a language of infinite size; can't it be handled simply by many @overloads in the stub file?
As of 3.6, the subprocess.check_output case is actually more complicated: it returns str if universal_newlines is True OR encoding is non-None, and bytes otherwise. I suppose existing overloads can take care of the encoding part: you'd have overloads for encoding is non-None returning str, universal_newlines=True returning str, and universal_newlines=False returning bytes.
But the main thing we really want is determining the correct return type when the argument is a compile-time literal. You can't overload on True or False because there's no way to spell "the subtype of bool that only contains True" (etc.). If it can't be determined what the value is at compile time it's okay to revert to the more general overload. Similar for open() and everything that takes a similar mode string (it's bred quite a following).
from pathlib import Path
# type function
def open_return_type(kwargs: Dict[str, object]) -> Type[object]:
assert isinstance(kwargs['mode'], str)
if 'b' in kwargs['mode']:
return IO[bytes]
else:
return IO[str]
def open(file: Union[str, bytes, int, Path], mode: str = 'r', buffering: int = -1,
encoding: str = None, errors: str = None, newline: str = None,
closefd: bool = ...) -> open_return_type: ...
It's flexible, and can be implemented like this:
TypeAnalyser.visit_unbound_type needs to allow "type functions" in function annotation
TypeChecker.check_return_statement, if it sees a type function, needs to call it, passing it the dict of all the arguments for which it knows literal values
if the type function returns a value, the type checker can use it as the return type
if the type function raises KeyError, the type checker should assume it relies on an argument that it doesn't know the value of; since it cannot infer the return type, it should do something sensible (e.g., issue a warning and use the return type based on the type context)
Reacted by Taine Zhao, Emil Stenström, allenlkq and Géry Ogam
How? Running arbitrary user code is certainly out of question. What do you suggest? To me this sounds like a turing-complete metaprogramming language and full-blown dependent type system.
I do like is the idea of avoiding the scoping problem (how to express def f(x, y:g(x)): ... using another function. But it will only be needed for fully-dependent type system, not for singleton-types.
@elazarg Ah yes, I did mean to run arbitrary user code. If the type checker is not allowed to do that for security or other reasons, my suggestion won't work.
It can't really do that in general, since there's nothing requiring type checkers to run in the same Python version as they're checking for, or even in Python at all.
@JukkaL Thank you for the response.
I've peeked into these example.
My understanding is that if i wanted to do that I'd have to write one function-hook for every function in my code that relies on this dependant type logic. Is this true ?
@mehdigmira Yes. The plugin approach is really only practical for library functions with relatively stable interfaces, in my opinion. Otherwise it will be too difficult to maintain.
Here's my current thinking on what's needed to support this in a minimally useful fashion.
Our current idea for the syntax is like this:
Type syntax is Literal[x], where x can be True, False, an integer literal or a string literal (not sure about bytes).
Literal[x, y] is equivalent to Union[Literal[x], Literal[y]].
Literal types are not inferred for variables unless the lvalue is final:
y: Literal['foo'] ='foo'# Without the annotation the type would be 'str'x: Final='foo'# Type is Literal['foo']z='foo'# Type of 'z' is 'str'zz: Final=z# The type of 'zz' is 'str' here as well
Note that the Final exception only applies to simple types:
a: Final= ['a'] # Inferred type is still List[str]
We'd likely promote a string literal to str in many/most contexts, but I'm not sure what the precise rules should be.
Literal types are compatible with the corresponding non-literal types, but not the other way around:
x='foo'y: Literal['foo'] =x# Errorz: str=y# Ok
For overloads we may need a fallback in addition to literal types to avoid false positives with legacy interfaces:
I really don't like the enforced Literal[...] syntax. Python type annotations are already annoyingly verbose and often hard to read, compared to, for example, typescript. I understand that strings are problematic, due to forward references, but couldn't at least booleans and integers (and potentially bytes) be abbreviated with just x: True or y: 42? That would open the possibility to have a potential __future__ import (that only works in combination with annotations) to use string literals as dependent types as well.
It's less verbose than @overloading every case, and perhaps more obvious than Literal[True]. Referring to the value of x in a type looks a bit weird [1], but that's what dependently-typed languages like Idris do [2], so it's not without precedent.
Here's a bigger example where the type depends on two arguments:
There's a bunch of ways TypeMapping can be made more expressive if desired. For example it could take a default parameter, making it like defaultdict. We could also make it more DSL-ish and allow more general pattern matching – I can post ideas for that in a follow-up if anyone's interested.
[1] Using a function argument in a type annotation would constrain this extension to python 3.7+ where type annotations aren't evaluated. This could be remedied with something like x = ArgVar('x') above the definition, but that could interfere with the actual code.
[2] For an example, see the printf function in typesafe printf
@srittau Something like the Literal[...] syntax is needed at least until Python 3.8, since things like Tuple[True, False] generate a runtime error at the moment. Bare literal as a type can be proposed for Python 3.8 or later. I think that we should start with something that works with already released Python versions, however.
@lubieowoce Your proposal introduces quite a bit of new syntax for functionality that @overload already supports. I think that there would have to be some important use cases that could be supported with the proposed syntax before it might be worth the extra implementation effort (and cognitive load). Verbosity does not sound like a very compelling argument to me, since I don't expect overloads involving literal types to be that common.
@JukkaL -- @Michael0x2a and me are already working on a draft-PEP about Final and Literal (it is not yet public, we will publish when we are relatively happy with the draft). Basically, it is just extended/detailed version of your comment above.
@lubieowoce -- Overloads (including type guards) is an important use case which will be explicitly mentioned in the draft PEP.
@srittau -- I don't think literal types will be used so often that saving 6 keystrokes is important. Second, I think explicit is better than implicit here, some people may be confused by bare literals in annotations. Finally, as Jukka mentioned, it just doesn't work.
Anyway, I would propose to pause the discussion here until the actual draft is posted here (hopefully very soon).
@ilevkivskyi@JukkaL thank you for your comments. I'm aware (and not too happy) that my proposal duplicates some functionality – I didn't mention it to keep the post short. I'll hold off with responding until you publish the draft PEP – perhaps it'll clarify some things that concerned me about the @overload+Literal approach.
Yes, mypy now supports literal types Literal[...].
Reacted by Nick Sweeting, Janka Uryga, Mike Panciera, Brad Hilton, karlicoss, Veith Röthlingshöfer, Andrew Sonin, Roman Inflianskas, Fidel Ramos and breandan
Activity
elazarg commentedon Mar 27, 2017
I think we can have
LiteralTypeas inLiteralType['w'],LiteralType[True]etc. I think it will be better than special-casing True/False/string. Maybe we will get simple constant propagation "falling out" of type aliases (which might be helpful for e.g. NamedTuple)I think regex will be problematic. How do you determine whether they overlap? String literals seems to suffice.
(And I don't think it counts as dependent type since it has little to do with runtime).
gvanrossum commentedon Mar 27, 2017
The mode string syntax for open() is more complicated than just
'r','w', etc. Hence the regex.I find
LiteralType[True]pretty verbose; also it's more general than we want to implement (we really don't want this to be a big project, just enough to handle the cases @JukkaL mentioned).If this doesn't count as a dependent type maybe you can explain your definition of dependent types? And what would you call this?
elazarg commentedon Mar 27, 2017
I'm not sure going into definitions is helpful, but the difference is (I guess) the reason you call it "Simple" dependent types - there nothing like
def foo(x: int, y: bar[x]) -> str if x == 5 else bool. There's only information immediately available at compile time. Instead of definingTrueto be a literal of typebool, we define it as being a literal of typeTrueTypewhich is a subtype of (or coercible to)bool. I don't see the difference between this and the kind of types we already have. Just because the type has precisely single value you have precise information about its runtime value, but this is already the case withNone,VoidandTuple[()].However, I might be wrong. I am also too easily dragged into issues of terminology.
sixolet commentedon Mar 27, 2017
elazarg commentedon Mar 27, 2017
I suspect special cases will continue to appear and build, and one day we will have to do this big project - which I have the feeling is not big right now, but will be big then and not easily backwards-compatible since special-casing is too easy to over fit for specific applications.
How complex is the mode for
open? Correct me if I'm wrong, but it is not a language of infinite size; can't it be handled simply by many@overloads in the stub file?sixolet commentedon Mar 27, 2017
Back when there were a couple interns working on this feature for a week, we had a few different use cases that they wanted to support:
open(a finite string-based language)pow(wanted to dispatch on positive vs. negative exponents)subprocess.check_outputWhat else? Is
powan odd one out here? Isopen? Or are these complicated ones rare, and in the common case you just want to dispatch on a constant?elazarg commentedon Mar 27, 2017
Maybe also
Tuple * 5... Maybepowcan be handled by having positive and negative integer "subset types".Any finite language is dispatch on a constant.
JelleZijlstra commentedon Mar 27, 2017
As of 3.6, the
subprocess.check_outputcase is actually more complicated: it returns str if universal_newlines is True OR encoding is non-None, and bytes otherwise. I suppose existing overloads can take care of the encoding part: you'd have overloads for encoding is non-None returning str, universal_newlines=True returning str, and universal_newlines=False returning bytes.gvanrossum commentedon Mar 27, 2017
But the main thing we really want is determining the correct return type when the argument is a compile-time literal. You can't overload on True or False because there's no way to spell "the subtype of bool that only contains True" (etc.). If it can't be determined what the value is at compile time it's okay to revert to the more general overload. Similar for open() and everything that takes a similar mode string (it's bred quite a following).
pkch commentedon Mar 28, 2017
I would prefer this syntax:
It's flexible, and can be implemented like this:
TypeAnalyser.visit_unbound_typeneeds to allow "type functions" in function annotationTypeChecker.check_return_statement, if it sees a type function, needs to call it, passing it thedictof all the arguments for which it knows literal valuesKeyError, the type checker should assume it relies on an argument that it doesn't know the value of; since it cannot infer the return type, it should do something sensible (e.g., issue a warning and use the return type based on the type context)elazarg commentedon Mar 28, 2017
How? Running arbitrary user code is certainly out of question. What do you suggest? To me this sounds like a turing-complete metaprogramming language and full-blown dependent type system.
I do like is the idea of avoiding the scoping problem (how to express
def f(x, y:g(x)): ...using another function. But it will only be needed for fully-dependent type system, not for singleton-types.pkch commentedon Mar 28, 2017
@elazarg Ah yes, I did mean to run arbitrary user code. If the type checker is not allowed to do that for security or other reasons, my suggestion won't work.
JelleZijlstra commentedon Mar 28, 2017
It can't really do that in general, since there's nothing requiring type checkers to run in the same Python version as they're checking for, or even in Python at all.
elazarg commentedon Mar 28, 2017
Just found this: https://typesandkinds.wordpress.com/#what-are-singletons
19 remaining items
JukkaL commentedon Oct 30, 2018
@mehdigmira The plugin API is not documented, so you are on your own if you want to go there. There are some examples at https://github.com/python/mypy/tree/master/mypy/plugins.
mehdigmira commentedon Oct 30, 2018
@JukkaL Thank you for the response.
I've peeked into these example.
My understanding is that if i wanted to do that I'd have to write one function-hook for every function in my code that relies on this dependant type logic. Is this true ?
JukkaL commentedon Oct 30, 2018
@mehdigmira Yes. The plugin approach is really only practical for library functions with relatively stable interfaces, in my opinion. Otherwise it will be too difficult to maintain.
JukkaL commentedon Oct 30, 2018
Here's my current thinking on what's needed to support this in a minimally useful fashion.
Our current idea for the syntax is like this:
Literal[x], wherexcan beTrue,False, an integer literal or a string literal (not sure about bytes).Literal[x, y]is equivalent toUnion[Literal[x], Literal[y]].Literal types are not inferred for variables unless the lvalue is final:
Note that the
Finalexception only applies to simple types:We'd likely promote a string literal to
strin many/most contexts, but I'm not sure what the precise rules should be.Literal types are compatible with the corresponding non-literal types, but not the other way around:
For overloads we may need a fallback in addition to literal types to avoid false positives with legacy interfaces:
cc @ilevkivskyi
srittau commentedon Oct 30, 2018
I really don't like the enforced
Literal[...]syntax. Python type annotations are already annoyingly verbose and often hard to read, compared to, for example, typescript. I understand that strings are problematic, due to forward references, but couldn't at least booleans and integers (and potentially bytes) be abbreviated with justx: Trueory: 42? That would open the possibility to have a potential__future__import (that only works in combination withannotations) to use string literals as dependent types as well.lubieowoce commentedon Nov 1, 2018
If we don't want to execute arbitrary Python code at the type level, how about specifying a value-to-type mapping with a dict? Something like this:
It's less verbose than
@overloading every case, and perhaps more obvious thanLiteral[True]. Referring to the value ofxin a type looks a bit weird [1], but that's what dependently-typed languages like Idris do [2], so it's not without precedent.Here's a bigger example where the type depends on two arguments:
There's a bunch of ways
TypeMappingcan be made more expressive if desired. For example it could take adefaultparameter, making it likedefaultdict. We could also make it more DSL-ish and allow more general pattern matching – I can post ideas for that in a follow-up if anyone's interested.[1] Using a function argument in a type annotation would constrain this extension to python 3.7+ where type annotations aren't evaluated. This could be remedied with something like
x = ArgVar('x')above the definition, but that could interfere with the actual code.[2] For an example, see the
printffunction in typesafe printfJukkaL commentedon Nov 1, 2018
@srittau Something like the
Literal[...]syntax is needed at least until Python 3.8, since things likeTuple[True, False]generate a runtime error at the moment. Bare literal as a type can be proposed for Python 3.8 or later. I think that we should start with something that works with already released Python versions, however.@lubieowoce Your proposal introduces quite a bit of new syntax for functionality that
@overloadalready supports. I think that there would have to be some important use cases that could be supported with the proposed syntax before it might be worth the extra implementation effort (and cognitive load). Verbosity does not sound like a very compelling argument to me, since I don't expect overloads involving literal types to be that common.ilevkivskyi commentedon Nov 1, 2018
@JukkaL -- @Michael0x2a and me are already working on a draft-PEP about
FinalandLiteral(it is not yet public, we will publish when we are relatively happy with the draft). Basically, it is just extended/detailed version of your comment above.@lubieowoce -- Overloads (including type guards) is an important use case which will be explicitly mentioned in the draft PEP.
@srittau -- I don't think literal types will be used so often that saving 6 keystrokes is important. Second, I think explicit is better than implicit here, some people may be confused by bare literals in annotations. Finally, as Jukka mentioned, it just doesn't work.
Anyway, I would propose to pause the discussion here until the actual draft is posted here (hopefully very soon).
brainrake commentedon Nov 1, 2018
@ilevkivskyi Would something like
Literal[x>0]work?ilevkivskyi commentedon Nov 1, 2018
No.
lubieowoce commentedon Nov 1, 2018
@ilevkivskyi @JukkaL thank you for your comments. I'm aware (and not too happy) that my proposal duplicates some functionality – I didn't mention it to keep the post short. I'll hold off with responding until you publish the draft PEP – perhaps it'll clarify some things that concerned me about the
@overload+Literalapproach.Michael0x2a commentedon Nov 20, 2018
Small update -- here's the draft PEP mentioned above: https://github.com/Michael0x2a/peps/blob/literal-types/pep-9999.rst.
I think the new plan is to try discussing it on our shiny new typing-sigs mailing list instead of here, though? We'll see how it goes.
emmatyping commentedon Jul 4, 2019
@Michael0x2a seeing as PEP 586 is accepted (congrats!) perhaps this can be closed, in favor of #5935?
JukkaL commentedon Jul 4, 2019
Yes, mypy now supports literal types
Literal[...].