PEP: 3117 Title: Postfix type declarations Version: $Revision$
Last-Modified: $Date$ Author: Georg Brandl <georg@python.org> Status:
Rejected Type: Standards Track Content-Type: text/x-rst Created:
01-Apr-2007 Python-Version: 3.0 Post-History:

Abstract

This PEP proposes the addition of a postfix type declaration syntax to
Python. It also specifies a new typedef statement which is used to
create new mappings between types and declarators.

Its acceptance will greatly enhance the Python user experience as well
as eliminate one of the warts that deter users of other programming
languages from switching to Python.

Rationale

Python has long suffered from the lack of explicit type declarations.
Being one of the few aspects in which the language deviates from its
Zen, this wart has sparked many a discussion between Python heretics and
members of the PSU (for a few examples, see [EX1], [EX2] or [EX3]), and
it also made it a large-scale enterprise success unlikely.

However, if one wants to put an end to this misery, a decent Pythonic
syntax must be found. In almost all languages that have them, type
declarations lack this quality: they are verbose, often needing multiple
words for a single type, or they are hard to comprehend (e.g., a certain
language uses completely unrelated[1] adjectives like dim for type
declaration).

Therefore, this PEP combines the move to type declarations with another
bold move that will once again prove that Python is not only
future-proof but future-embracing: the introduction of Unicode
characters as an integral constituent of source code.

Unicode makes it possible to express much more with much less
characters, which is in accordance with the Zen <20> ("Readability
counts."). Additionally, it eliminates the need for a separate type
declaration statement, and last but not least, it makes Python measure
up to Perl 6, which already uses Unicode for its operators.[2]

Specification

When the type declaration mode is in operation, the grammar is changed
so that each NAME must consist of two parts: a name and a type
declarator, which is exactly one Unicode character.

The declarator uniquely specifies the type of the name, and if it occurs
on the left hand side of an expression, this type is enforced: an
InquisitionError exception is raised if the returned type doesn't match
the declared type.[3]

Also, function call result types have to be specified. If the result of
the call does not have the declared type, an InquisitionError is raised.
Caution: the declarator for the result should not be confused with the
declarator for the function object (see the example below).

Type declarators after names that are only read, not assigned to, are
not strictly necessary but enforced anyway (see the Python Zen:
"Explicit is better than implicit.").

The mapping between types and declarators is not static. It can be
completely customized by the programmer, but for convenience there are
some predefined mappings for some built-in types:

  Type        Declarator
  ----------- --------------------------------------------------
  object      � (REPLACEMENT CHARACTER)
  int         ℕ (DOUBLE-STRUCK CAPITAL N)
  float       ℮ (ESTIMATED SYMBOL)
  bool        ✓ (CHECK MARK)
  complex     ℂ (DOUBLE-STRUCK CAPITAL C)
  str         ✎ (LOWER RIGHT PENCIL)
  unicode     ✒ (BLACK NIB)
  tuple       ⒯ (PARENTHESIZED LATIN SMALL LETTER T)
  list        ♨ (HOT SPRINGS)
  dict        ⧟ (DOUBLE-ENDED MULTIMAP)
  set         ∅ (EMPTY SET) (Note: this is also for full sets)
  frozenset   ☃ (SNOWMAN)
  datetime    ⌚ (WATCH)
  function    ƛ (LATIN SMALL LETTER LAMBDA WITH STROKE)
  generator   ⚛ (ATOM SYMBOL)
  Exception   ⌁ (ELECTRIC ARROW)

The declarator for the None type is a zero-width space.

These characters should be obvious and easy to remember and type for
every programmer.

Unicode replacement units

Since even in our modern, globalized world there are still some
old-fashioned rebels who can't or don't want to use Unicode in their
source code, and since Python is a forgiving language, a fallback is
provided for those:

Instead of the single Unicode character, they can type
name${UNICODE NAME OF THE DECLARATOR}$. For example, these two function
definitions are equivalent:

    def fooƛ(xℂ):
        return None

and :

    def foo${LATIN SMALL LETTER LAMBDA WITH STROKE}$(x${DOUBLE-STRUCK CAPITAL C}$):
        return None${ZERO WIDTH NO-BREAK SPACE}$

This is still easy to read and makes the full power of type-annotated
Python available to ASCII believers.

The typedef statement

The mapping between types and declarators can be extended with this new
statement.

The syntax is as follows:

    typedef_stmt  ::=  "typedef" expr DECLARATOR

where expr resolves to a type object. For convenience, the typedef
statement can also be mixed with the class statement for new classes,
like so:

    typedef class Foo☺(object�):
        pass

Example

This is the standard os.path.normpath function, converted to type
declaration syntax:

    def normpathƛ(path✎)✎:
        """Normalize path, eliminating double slashes, etc."""
        if path✎ == '':
            return '.'
        initial_slashes✓ = path✎.startswithƛ('/')✓
        # POSIX allows one or two initial slashes, but treats three or more
        # as single slash.
        if (initial_slashes✓ and
            path✎.startswithƛ('//')✓ and not path✎.startswithƛ('///')✓)✓:
            initial_slashesℕ = 2
        comps♨ = path✎.splitƛ('/')♨
        new_comps♨ = []♨
        for comp✎ in comps♨:
            if comp✎ in ('', '.')⒯:
                continue
            if (comp✎ != '..' or (not initial_slashesℕ and not new_comps♨)✓ or
                 (new_comps♨ and new_comps♨[-1]✎ == '..')✓)✓:
                new_comps♨.appendƛ(comp✎)
            elif new_comps♨:
                new_comps♨.popƛ()✎
        comps♨ = new_comps♨
        path✎ = '/'.join(comps♨)✎
        if initial_slashesℕ:
            path✎ = '/'*initial_slashesℕ + path✎
        return path✎ or '.'

As you can clearly see, the type declarations add expressiveness, while
at the same time they make the code look much more professional.

Compatibility issues

To enable type declaration mode, one has to write:

    from __future__ import type_declarations

which enables Unicode parsing of the source[4], makes typedef a keyword
and enforces correct types for all assignments and function calls.

Rejection

After careful considering, much soul-searching, gnashing of teeth and
rending of garments, it has been decided to reject this PEP.

References

Acknowledgements

Many thanks go to Armin Ronacher, Alexander Schremmer and Marek Kubica
who helped find the most suitable and mnemonic declarator for built-in
types.

Thanks also to the Unicode Consortium for including all those useful
characters in the Unicode standard.

Copyright

This document has been placed in the public domain.



  Local Variables: coding: utf-8 mode: indented-text indent-tabs-mode:
  nil sentence-end-double-space: t fill-column: 70 End:

EX1

    https://mail.python.org/pipermail/python-list/2003-June/210588.html

EX2

    https://mail.python.org/pipermail/python-list/2000-May/034685.html

EX3

    http://groups.google.com/group/comp.lang.python/browse_frm/thread/6ae8c6add913635a/de40d4ffe9bd4304?lnk=gst&q=type+declarations&rnum=6

[1] Though, if you know the language in question, it may not be that
unrelated.

[2] Well, it would, if there was a Perl 6.

[3] Since the name TypeError is already in use, this name has been
chosen for obvious reasons.

[4] The encoding in which the code is written is read from a standard
coding cookie. There will also be an autodetection mechanism, invoked by
from __future__ import encoding_hell.