Hacker News new | past | comments | ask | show | jobs | submit login

This module doesn't seem to make it possible to type-annotate map and filter, which are Python builtins.

I believe that dynamic typing results from frustration over generics, rather than over static typing in general. (Type stuttering is another problem, but modern languages solve it with local type inference.) I mean, annotating non-generic functions is always straightforward doesn't introduce much complexity:

    # s should be str
    # returns nothing
    def print(s):
        ...

    # just becomes (using Py3 annotation syntax)
    def print(s: str) -> None:
        ...
However, it's much harder to write a type annotation of generic functions. Worse, remember that Python's map is variadic.

    # for any types t0, t1, t2, ... tn:
    # fn is a function that takes arguments of types t0, t1, ... t(n-1)
    # and returns a value of type tn;
    # seqs are iterables of types t0, t1, ... t(n-1);
    # returns a list of type tn.
    def map(fn, *seqs):
        ...

    # How to write this signature is non-obvious
Many mature static typing systems would allow you to express such types (usually called parameterized types). But any one of them would require more than those trivial notations in print.

That's when the dynamic typing people get annoyed and go "fxxk static typing systems, I can handle this in my mind". Which is about 65% (totally random estimation) the point of dynamic typing in my opinion.

Any type checker for dynamic typed languages that doesn't seriously try to solve the generics problem is not genuinely interesting.




Even that trivial `print` annotation breaks duck-typing; the print function can actually accept anything that responds to `__str__`.

Similarly, is there any reason the example `add` function in the OP shouldn't be able to add strings, or lists?

I've seen a lot of these quick-runtime-type-checking libraries, but they always have the same problem as manual type-checking: they make the constraints much stricter than they need to be and prevent entire classes of useful behavior.


In some languages, like Scala, you can use structural type annotations to handle this situation.


Python has the abc module, but it would be a bit wordier than "arg=list". Maybe if the builtins were treated as the corresponding abcs.


I think `abc` would be overkill for this. My understanding is that you might use that to add behavior to a bunch of classes that aren't related hiearchically. But for type safety, all you need to do is check for the single magic method.


You can use it for that, but you can also use it to check for rough interfaces. (The classes are rigged to fool isinstance() via dark magic.) It's fairly common that you want more than one magic method, but don't care too much what the type is as long as it's sorta like a `list` — thus, MutableMapping.


There is nothing special about map and filter, they are not idiomatic Python because there are better ways to do the same things.

Python 3 annotations are already not used in any specific predetermined way, so if you create a system to do something with type annotations then you can implement List(str) or whatever it is you want an annotation to work like.


> There is nothing special about map and filter, they are not idiomatic Python because there are better ways to do the same things.

True, list comprehension is often used instead. But a complete typing system should be able to type-check that too. The point is not changed.

Also, I only used map and filter as an example. Python programmers tend to write a lot of generics functions without actually knowing it.

    def f(g, *args):
        ...
        r = g(*args)
        ...
        return r
This function is actually quite generic, akin to map.

And we are not yet getting into the realm of "trait" or "concept" or "typeclass" or whatever.

    def sum(*nums):
        s = 0
        for n in nums:
            s += n
        return s
Remember, although s is definitely an int, s + n may yield some completely unrelated type if n implements __radd__. The function here involves potentially infinite number of types, and their __add__ or __radd__ traits (reusing the name of the Python magic method). To fully express its type without unnecessary constraints is an interesting exercise. Since Python allows dynamic parameter list building (like sum(*nums)), dependent typing may be necessary for that purpose.


Are there languages that include the strong type checking, inference, and typeclass functionality of Haskell while also being a little more forgiving in terms of purity, so that writing ordinary programs is not quite so troublesome as in Haskell?


Rust has a decent amount of type inference, traits and strong guarantees.

Ocaml/SML are both functional languages similar to haskell, and allow IO pretty much anywhere. They don't have typeclasses, (although ML people would say there's nothing you can't do with the ML module system[0]).

[0]http://lambda-the-ultimate.org/node/1558


Scala (although it has limited type inference to require people to specify at least the inputs to methods for readability).


> This module doesn't seem to make it possible to type-annotate map and filter, which are Python builtins.

In Haskell map is

    map :: (a->b) -> [a] -> [b]
How could we do something like that in a Python type extension? One way would be to just use the Haskell notation in quotes:

    @type("(a->b) -> [a] -> [b]")
    def myMap(fn, as):
        #...etc...
An alternative would be to define a function Fun for describing functional types, and gen.{something} for a generic type, e.g.:

    @type(Fun(gen.a, gen.b), [gen.a], ret=[gen.b])
    def myMap ...etc...
But that notation doesn't (at least to me) look as clean as the Haskell.


You have a strange definition of interesting. Even dynamically typed languages are used in a static way most of the time; this is why JavaScript engines can perform well on most code. A type system without generics can still find plenty of bugs.


I'd agree with his definition and your point simultaneously. Type system with only 0-th order types are still useful for optimization and error capture, but higher order structures and higher order polymorphism are both incredibly useful and more powerful than 0-th order types.


It's not hard to imagine an extension to ML-style types that could cover variadicity:

    map : ('a... -> 'b) -> ('a list)... -> 'b list
Where <type>... denotes <type1> -> <type2> -> ... -> <typeN> spliced into the type signature, with each type variable 'a in <type> replaced with 'a1, 'a2, ... 'aN. You could cover tuples too:

    zip : ('a list)... -> 'a,,, list
Bit ugly, and writing a type checker for it could be fun, but it seems workable.


Take a look at Typed Racket for a variation on this.



Typed Racket is pretty decent.

However, behold the byzantine Scheme numerical tower and the effort to type it: http://www.ccs.neu.edu/home/stamourv/papers/numeric-tower.pd...


Fully typing variadicity in Python is only possible with dependent typing, since Python allows you to build the argument list dynamically. Generally speaking you can't even determine the number of arguments statically:

    args = []
    while some_condition():
        args.append('x')
    f(*args)
Typed Racket allows you type some common cases, though.


"type stuttering" eludes my ability to Google. Could you provide a link or a quick explanation?


    uint8_t foo = (uint8_t)69;
In theory, there should be no need to specify the type twice. The ideal circumstance is something like

    uint8_t foo = 69;


Until java 7 you had to write:

    List<String> list = new ArrayList<String>();
It feels silly to have to write the type parameter twice. But now you can say:

    List<String> list = new ArrayList<>();
It's usually not a huge deal, but occasionally the stutter can be more pronounced:

    Map<String, List<Map<Integer, Set<Float>>>> map = ...




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: