> so here is the roadmap with exemple for each step...
I think coming up with a roadmap is a great idea. There's no point coming up with a Grand Unified Type System without a practical means of getting from here to there.
I think this latest essay gives a much better idea of where Guido is thinking of heading, which means it should be feasible to start plotting a route.
> 1 - adaptation
I agree that this should be number 1 on the list. One thought is whether it would be useful for adapt() to include a 'strict' keyword argument (or provide a separate adapt_strict() function). Strict adaption would require that the result be an actual instance of the specified type.
Then type declarations would be syntactic sugar for adaptation - normal adaptation (duck typing) in most cases, or adaptation plus a type-check for strict adaptation (e.g. using Guido's syntax of adding the 'class' keyword to the type declaration).
> 2 - standard type checking decorators
Again, I think this is a nice way of trying out the machinery before changing the syntax. I see it as somewhat equivalent to the cumbersome repeat-the-name-three-times decorator syntax that existed prior to Python 2.4
Standard accepts() and returns() decorators should naturally be based on adaptation. They might also populate Guido's proposed "__signature__" function attribute.
If the adaptation PEP is updated to include the concept of 'strict' adaptation, there is the question of how that can be incorporated into these decorators.
> 3 - advanced type operations
I'd prefer to see this concept integrated with the adaptation PEP before moving down the operator syntax path.
The associated concept for "|" is "adapt_to_any", which could be obtained simply by allowing a tuple for the protocol argument to adapt(), and try to adapt to the protocol in left-to-right order (ala isinstance()). As soon as the supplied object is adapted successfully, the adapted result is returned. For example (using a special type, rather than a plain tuple_:
adapt(x, any(prot1, prot2))
to mean:
for prot in (prot1, prot2):
try:
result = adapt(x, prot)
return result
except AdaptationError:
pass
An appropriate definition of "adapt_all" (for "&") in terms of adaptation is slightly less obvious. The simplest seems to be to handle it as a nested adaptation to each of the given protocols. That is:
adapt(x, all(prot1, prot2))
to mean:
adapt(adapt(x, prot1), prot2)
Finally, there is the proposed operator syntax for typing a tuple ("*"). Again, this can be done manually initially to determine if there is enough demand to make it operator based:
adapt(x, each(prot1, prot2))
to mean:
(adapt(y, prot) for y, prot in zip(x, (prot1, prot2)))
Assume any(), all() and each() are appropriate classes from an adaptation module.
> 4 - interface/protocolshave a nice way to define
> protocols (I think this term is nearer of the exact
> purpose: definig a behaviour).
Interfaces certainly seem like a nice way of formalising Python's duck-typing.
However, if interfaces are pursued, adaptation should, as a last resort, inspect the supplied object to see if it actually supports the methods and attributes required by the interface, and then allow the adaptation to succeed with the original object.
> 5 - pre/post condition
This is again something that could be done with decorators now, and a couple of magic function attributes that get invoked automatically if present (e.g. __pre__ and __post__):
def pre(orig):
def bind_pre(func):
orig.__pre__ = func
return orig
return bind_pre
def post(orig):
def bind_post(func):
orig.__post__ = func
return orig
return bind_post
def foo(x):
# Main body
@pre(foo)
def foo(x):
# precondition
@post(foo)
def foo(x, result):
# postcondition
> 5 - parametrised type
I like Guido's idea of using type.__getitem__ to handle parameterisation of a type. However, I think it should be up to type instances to determine exactly what the parameter list means.
Going back to adaptation again, consider an optional third argument to the __adapt__ special method that contains the tuple of type parameters. Types that want to be parameterisable declare __adapt__ with the extra argument (defaulting it to None so normal adaptation can still work). Types that aren't parameterisable simply use the standard __adapt__ signature.
type.__getitem__ returns an interface that delegates to the type instance's standard __adapt__ method with that 3rd argument populated. For instance:
adapt(x, list) -> list.__adapt__(x)
adapt(x, list[int]) -> list.__adapt__(x, (int,))
The result of the __getitem__ call would presumably be some special object with an appropriate __adapt__ method (again, accessible through an adaptation module). For example:
class parameterised_type(object):
def __init__(self, cls, types):
self._cls = cls
self._types = types
def __adapt__(self, obj):
self._cls.__adapt__(obj, self._types)
class type:
def __getitem__(self, item):
if isinstance(item, tuple):
return parameterised_type(self, item)
else:
return parameterised_type(self, (item,))