(This one took longer to respond to because there are so many nits. :-)

On Sat, Mar 21, 2015 at 8:37 AM, Steven D'Aprano <steve@pearwood.info> wrote:
On Fri, Mar 20, 2015 at 09:59:32AM -0700, Guido van Rossum wrote:

> Union types
> -----------
[...]
> As a shorthand for ``Union[T1, None]`` you can write ``Optional[T1]``;

That only saves three characters. Is it worth it?

Yes (see other messages in the thread).
 
> An optional type is also automatically assumed when the default value is
> ``None``, for example::
>
>   def handle_employee(e: Employee = None): ...

Should that apply to all default values or just None? E.g. if I have

    def spam(s: str = 23): ...

should that be inferred as Union[str, int] or be flagged as a type
error? I think that we want a type error here, and it's only None that
actually should be treated as special. Perhaps that should be made
explicit in the PEP.

I only want this special treatment for None.  The PEP seems to be pretty clear about this.
 
[...]
> For the purposes of type hinting, the type checker assumes ``__debug__``
> is set to ``True``, in other words the ``-O`` command-line option is not
> used while type checking.

I'm afraid I don't understand what you are trying to say here. I would
have expected that __debug__ and -O and the type checker would be
independent of each other.
 
Well, __debug__ tracks -O (__debug__ is false iff -O is given). But a type checker usually doesn't have the luxury to know whether the code is meant to run with -O or without it. So it assumes -O is *not* given, i.e. __debug__ is True.

(You could try to type-check twice, once with -O and once without, but this idea doesn't really scale when you consider all the other flags that might be treated this way. And it's not really worth the trouble.)
 
[...]
> To mark portions of the program that should not be covered by type
> hinting, use the following:
>
> * a ``@no_type_check`` decorator on classes and functions
>
> * a ``# type: ignore`` comment on arbitrary lines
>
> .. FIXME: should we have a module-wide comment as well?

I think so, if for no other reason than it will reduce the fear of some
people that type checks will be mandatory.
 
That fear ought to be reduced to zero by words in the PEP; have you got any suggestions? The mere presence of a module-wide comment to disable type checks might actually *increase* the fear that (absent such a comment) type checks might be mandatory.

I could imagine other reasons for wanting a file-scoped directive, but I'm not sure -- there's a lot of discussion in https://github.com/ambv/typehinting/issues/35, maybe you can make sense of it.
 
> Type Hints on Local and Global Variables
> ========================================
>
> No first-class syntax support for explicitly marking variables as being
> of a specific type is added by this PEP.  To help with type inference in
> complex cases, a comment of the following format may be used::
>
>   x = []   # type: List[Employee]
>
> In the case where type information for a local variable is needed before
> it is declared, an ``Undefined`` placeholder might be used::
>
>   from typing import Undefined
>
>   x = Undefined   # type: List[Employee]
>   y = Undefined(int)

How is that better than just bringing forward the variable declaration?

    x = []   # type: List[Employee]
    y = 0

The actual initialization might have to happen later, separately in different branches of an if-statement; or this might be a class variable. Jukka gave some more reasons for having Undefined in https://github.com/ambv/typehinting/issues/20
 
> Casts
> =====
>
> Occasionally the type checker may need a different kind of hint: the
> programmer may know that an expression is of a more constrained type
> than the type checker infers.  For example::
>
>   from typing import List
>
>   def find_first_str(a: List[object]) -> str:
>       index = next(i for i, x in enumerate(a) if isinstance(x, str))
>       # We only get here if there's at least one string in a
>       return cast(str, a[index])
>
> The type checker infers the type ``object`` for ``a[index]``, but we
> know that (if the code gets to that point) it must be a string.  The
> ``cast(t, x)`` call tells the type checker that we are confident that
> the type of ``x`` is ``t``.

Is the type checker supposed to unconditionally believe the cast, or
only if the cast is more constrained than the infered type (like str and
object, or bool and int)?

E.g. if the type checker infers int, and the cast says list, I'm not
entirely sure I would trust the programmer more than the type checker.

My feeling here is that some type checkers will unconditionally trust
the cast, and some will flag the mismatch, or offer a config option to
swap between the two, and that will be a feature for type checkers to
compete on.

It should unconditionally believe the cast. (Reference: https://github.com/ambv/typehinting/issues/15#issuecomment-69136820)
 
I'm also going to bike-shed the order of arguments. It seems to me that
we want to say:

    cast(x, T)  # pronounced "cast x to T"

rather than Yoda-speak "cast T x to we shall" *wink*. That also matches
the order of isinstance(obj, type) calls and makes it easier to
remember.
 
 Seems you're not alone here. :-) I've opened https://github.com/ambv/typehinting/issues/63
 
> At runtime a cast always returns the
> expression unchanged -- it does not check the type, and it does not
> convert or coerce the value.

I'm a little concerned about cast() being a function. I know that it's a
do-nothing function, but there's still the overhead of the name lookup
and function call. It saddens me that giving a hint to the type checker
has a runtime cost, small as it is.

(I know that *technically* annotations have a runtime cost too, but
they're once-only, at function definition time, not every time you call
the function.)

Your point below that cast() can be used inside expressions is a valid
point, so there has to be a cast() function to support those cases, but
for the example given here where the cast occurs in a return statement,
wouldn't a type comment do?

    return some_expression  # type: T

hints that some_expression is to be treated as type T, regardless of
what was infered.
 
That has slightly different semantics -- while the type checker should unconditionally believe cast(), #type: comments are required to be consistent.

In the example from the PEP (find_first_str()) the cas() is required, since the derived type is object.
 
> Casts differ from type comments (see the previous section).  When
> using a type comment, the type checker should still verify that the
> inferred type is consistent with the stated type.  When using a cast,
> the type checker trusts the programmer.  Also, casts can be used in
> expressions, while type comments only apply to assignments.


> Stub Files
> ==========
[...]
> Stub files may use the ``.py`` extension or alternatively may use the
> ``.pyi`` extension.  The latter makes it possible to maintain stub
> files in the same directory as the corresponding real module.

I don't like anything that could cause confusion between stub files and
actual Python files. If we allow .py extension on stub files, I'm sure
there will be confusing errors where people somehow manage to get the
stub file imported instead of the actual module they want.

Is there any advantage to allowing stub files use a .py extension? If
not, then don't allow it.


--
--Guido van Rossum (python.org/~guido)