Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This article talks about Python getting type hints. There as a HN post recently about Ruby getting a type checker (https://news.ycombinator.com/item?id=17217815)

I think the debate is largely going in favor of static type checking. The widespread adoption of type inference in many statically typed languages (var in Java, auto in C++, let in Rust) has greatly eased the ergonomics of using a statically typed language.

Unless there is a massive ecosystem that you need to take advantage of (for example javascript), in 2018 greenfield development should probably be in a statically typed language.



My dream back in 2008 or so was a language with smart enough type inference and probably some kind of gradual typing that allowed you to bridge the gap from static and dynamic typing -- dynamic for the prototyping, REPL, quick scripts and very high level code, but as you go down the layers into lower-level, more performance-sensitive code you could add more and more typing and machine-specific hacks.

Type inference might got one halfway there, but it would work best at the call site -- the type of variables could be inferred from return values and from literals and so on. But you'd still want to define a function as basically returning "Any". This means a caller wouldn't have a type for the return value, and so if you want to do "fileName := funcReturningAny(); openFile(fileName)" and openFile() was defined only for a string argument, the compiler would have to generate a runtime check at the call site to assert the type fileName as being a strong. This the call site is penalized for being typeless, but the statically typed code wouldn't be.

I suspect this would work pretty well. You can start out with no types, and you'd be able to add types as you go, slowly tightening the screws on your program. Generics, traits and other modern abstractions would still be available, though some of those features would start to become unavailable or awkward to use with any code not sufficiently tightened with types.

I don't know of any language that does anything like this, although I suppose TypeScript is pretty close.


Sounds like what Perl 6 does :) You can add optional type signatures, and you get compile-time checks disallowing impossible usages of these:

  > sub takes-string(Str $foo) { }
  sub takes-string (Str $foo) { #`(Sub|94327719210888) ... }
  > takes-string(5); say "Hi!"
  ===SORRY!=== Error while compiling:
  Calling takes-string(Int) will never work with declared signature (Str $foo)
  ------> <BOL>⏏takes-string(5); say "Hi!"


Basically have already lived this dream with Haxe. However the cross-platform tooling side of Haxe has historically been the fly in the ointment since it sits above the "real runtime" of whatever you targeted, creating all sorts of bits of friction. And it doesn't cross over into "systems" stuff - it's always within a GC'd environment.


Dynamic in C# is pretty much what you describe!


Pretty much nobody does this, but you certainly could. I'm not sure that all that DLR work that was put in gets a ton of real usage, particularly with how C# has grown language features since then.


Agreed. The majority are beginning to realize that strong type systems are actually helpful instead of a mandatory annoyance.

Most programming I've done in the past has been with python (no type hinting) or languages with weak static type systems (java et al). Having to verbosely specify the types in java was always just painful, while the upfront speed and freedom of python felt much nicer. However after getting a taste of better type systems by dabbling in rust and elm, now every time I write something in python I'm thinking "was argument a string or a foo class?" or "that runtime error would not have happened if the type checker caught it".

Python type hints now are a huge plus for the language. It still seems a bit "hacky" though. I'd love to see something that adds more syntax sugar and feels more built in. Maybe it's just me.


I would be like to be able to specify types at integration points (module level, function level, etc).

Function arguments do benefit from clear inputs and outputs (is this type X? a list of X? A list of anything? Is it returning Y? List of Y? Nothing at all? An error or nothing? Something that is just like X but with an extra field?)

Inside the function? Leave me alone. You are a computer, go figure out. Which seems to be the direction we are heading towards.

> was argument a string or a foo class

Oh, it was supposed to be a string. However, it is a string that came from the UI, so it is untrusted, so your SQL function should refuse to use it. But hey, feel free to lowercase it.

The main problem is that usually type systems are not very rich, so the baby gets thrown out with the bathwater.

Also, runtime errors are ok if they happen in your test cases. You do have them, right? :)


> Also, runtime errors are ok if they happen in your test cases.

Yes, but if some errors can be caught sooner by your linter/IDE/typechecker then it speeds up development. Would you rather be notified of a type mismatch immediately after saving the file or later when you run the test suite?


Yeah, my only experience with static typing early on was C and Java, and it turned me off the whole thing. I could understand the advantage in C for the whole close-to-the-machine thing, but Java's verbosity and poor expressiveness while allowing NPEs everywhere felt really silly.

Idris is what's bringing me back to typed land.


For javascript you can use typescript, which brings the joy of a modern type system to es6 and allows you to migrate file by file.


See also flow [1] which does the same and allows you to migrate function by function. :)

[1]: https://flow.org/


The lesson is rather that optional static typing is useful. The programmer should be able to choose based on what makes sense for the task. Strictly statically typed languages preclude the option of going dynamic.


>Strictly statically typed languages preclude the option of going dynamic.

No they don't; even in C it's possible to just type everything as void*, and everything as Object in Java. Just nobody does this 'cos it introduces a bunch of unnecessary runtime errors.


To call a method on a void* or a Java Object you first have to explicitly cast it to a type that has that method, which means that you need to declare a common interface and have each object explicitly implement that interface. Duck typing in dynamic languages doesn't require that level of ceremony.


This is the lesson I've learned in Clojure. We have varying levels of data shape specification, contracts, and static type checking available a-la-carte when you want it but not getting in your way when you don't.


I agree, and yet dynamic languages such as Python continue to gain popularity because they are supposedly easier for newbies to pick up. I think we (experienced software developers) have to do a better job of educating newcomers about the dangers of dynamic typing.

https://stackoverflow.blog/2017/09/06/incredible-growth-pyth...


Why do you think static languages are easier to pick up? I have had the opposite experience. In managing people who do not aspire to be software developers, but do need to write simple programs to perform basic job functions, I’ve seen much more openness to Python than to more structured alternatives. If you have suggestions that set the bar for working code lower than we can get with Python then I would love to hear them.


I think Python is perceived as easy to pick up because it's a scripting language, not because it's dynamically typed. I wish I could recommend a statically-typed scripting language that's easy to pick up, but I'm not familiar with any mainstream ones (and I'm probably too far from the newbie stage myself to be a good judge).


It's not even the dynamic typing that makes Python easy to pick up. It's readable core language, and a well-documented and reasonably thorough standard library that doesn't ignore things that are useful specifically for teaching people (like turtle graphics - I've yet to see a better way to teach loops, recursion etc than getting someone to draw stars and spirals with a turtle).


The more TypeScript I write, the less I like Python. In a recent project that involved pulling some data from Hive via Pandas dataframes and merging it with data from a JSON REST API, then doing some calculations and stuffing that data into a different JSON REST API (with a totally different shape to the data), I kept running into little edge cases that Python forces you to handle explicitly vs Javascript just doing what you want. It also annoys me to an irrational degree that Python has "dicts" and not objects, "lists" and not arrays, KeyError everywhere, error looking up dict keys as ints (you can only look them up by str with no implicit type conversion), can't say "dict.keyname", have to do dict['keyname'] or usually dict.get('keyname') and handle things like dicts within dicts where you're not sure if keys exist even more verbosely, etc

example of something annoying that came up:

    foo = [1,2,3]
    bar = {'1': 1, '2': 2, '3': 3}
    for i in foo:
      print(bar.get(i))
What does it output? None x3. In order to get it to do the thing, I did something like this:

    foo = [1,2,3]
    bar = {'1': 1, '2': 2, '3': 3}
    for i in foo:
        str_i = str(i)  # even more fun when this throws
        print(bar.get(str_i))
I use python because most of my coworkers understand it and it has wide library + runtime support where I work, but it's definitely not my favorite thing to work with. Dynamic typing where types still matter and where the language is picky about implicit type conversions kind of sucks.

I also couldn't do:

    foo = [1,2,3]
    bar = {'1': 1, '2': 2, '3': 3}
    for str(i) in foo:
        print(bar.get(i))
Which was also eyeroll. Like, I get it, but...c'mon. Working with Pandas is also more or less learning another language, with the insanity that is df.T.whatever and df.loc[]:

    df.loc[df['shield'] > 6, ['max_speed']]
I'm sure that someone will comment about how I'm an idiot and there's a much better way to do it, but given PEP20:

    There should be one-- and preferably only one --obvious way to 
    do it. Although that way may not be obvious at first unless 
    you're Dutch.
I guess I'm not Dutch enough.


So, in a discussion where most complaints about Python are that it is dynamically typed (vs statically typed eg Java, Go, Rust etc) you're complaining that it is strongly typed (eg vs weakly typed eg Javascript)?

I don't have a problem with that - it just seemed kinda incongruous in that context. I imagine the other critics would be even more unhappy about implicit type conversions.


dicts are like Maps in JavaScript, so the type of a key matters. It enables usecases where your keys aren't always strings:

  >> bar = {1: "a", "1": "b"}
  >> bar[1]
  "a"
  >> bar["1"]
  "b"


I'm not familiar with Pandas but I'm guessing that its API is similar in style to numpy just by looking at the code snippet. I can't rule out that you might be an idiot, but it is not because you find that line of code confusing. The numpy API is horrific. Its abuse of operator overloading and undermining of expectations is shockingly awful. They should have just made a separate query language.


Why should it implicitly convert a number to a string? That's a common source of bugs. Explicit is definitely better.

Why not just do the following?

    foo = [1,2,3]
    bar = {'1': 1, '2': 2, '3': 3}
    for i in foo:
        print(bar[str(i)])


> I guess I'm not Dutch enough.

I could write an "obvious" fix for your example, but I expect that it'd be unsatisfactory, since the real problem likely has a different solution.

It looks like you might want a little utility to transform key lookups to a standard. Web frameworks do stuff like that for mapping URLs to functions, to ignore case, etc.

You might be interested in learning about the ``dict.__missing__`` magic method.


No, the problem you had was not because of Python, but because of JavaScript, JavaScript's objects only let you put strings inside of object keys, and JSON inherited that, it would have been more sensible for bar to come from the API like this:

    bar = {1: 1, 2: 2, 3: 3}


At least it's safer than newbies picking up javascript or php. ;)


> Unless there is a massive ecosystem that you need to take advantage of (for example javascript), in 2018 greenfield development should probably be in a statically typed language.

I find this sort of generalizations off-putting. It is all about trade offs.

Types can be very useful. There are whole categories of errors that are avoided by types.

And yet, in many cases they are a productivity drain. Time is often better spend by writing testcases instead of chasing down type issues(more so if a language has a 'generics' or C++ templates concept), so that approach got favored in many fields. These will catch type errors, and static languages also need tests, so the gains are not so easily quantifiable.

It is not just a matter of ergonomics, it is a matter of getting out of the way of the problem domain. Most people are paid to solve actual issues, not to craft convoluted C++ templates, custom types or deep type chains.

Type inference helps, but even Haskellers will often avoid it when defining functions, so that they will be explicit. But Haskell has incredibly powerful constructs. I was amazed that you can actually tell the compiler you are writing a recursive data structure[1], which is something that most languages won't allow you to do – the most they will allow you is to define a reference to the same type of object(or 'struct'), and you have to stitch that together in runtime. Simple cases are also simple. A function that gets a list and returns an element of that list? [a] -> a, I don't care what a is, nor does the compiler. But feed it something that is not a list, and it will complain.

A language should let you easily define new types, and allow these to be used without friction by standard functions. For instance: if I have an integer, is this integer describing a temperature value? If so, functions that work with speed should refuse to operate with this data. Standard mathematical functions _should not care_ and should do whatever operations we request(without any silly type casting, I told the compiler this thing is a number, use it). The compiler optimizer should also use this information to generate appropriate assembly instructions.

So yes, if you have an expressive type system, types can be a joy (you'll find yourself thanking the compiler often). If instead your type is spent trying to figure out why IEnumberable<Something> cannot be converted to IEnumerable<SomethingElse>, even though they are subclasses and even though the same operations work with arrays, then types are getting into the way of solving real problems[2][3].

And even with an expressive type system, there is a non trivial cognitive cost. Sometimes you just want to write (map (lambda (x) (do-something x)) lst) and not really care what it is that you are manipulating. On the other hand, I personally do prefer using Golang to talk to AWS apis (instead of python or javascript), precisely because of their deeply nested type hierarchies.

Being able to sprinkle type annotations where it is useful and have the compiler figure out as much as possible otherwise is a good compromise, which is the approach that the Python article talks about.

I would read this as not favoring mandatory static type checking in 2018 but, instead, using a hybrid mechanism that benefits from both approaches.

[1] http://tuttlem.github.io/2013/01/04/recursive-data-structure... [2] https://blogs.msdn.microsoft.com/rmbyers/2005/02/17/generic-... [3] http://www.baeldung.com/java-type-erasure


>Time is often better spend by writing testcases instead of chasing down type issues(more so if a language has a 'generics' or C++ templates concept), so that approach got favored in many fields. These will catch type errors, and static languages also need tests, so the gains are not so easily quantifiable.

Assuming an equivalent program written in a statically-checked language and a dynamically-checked language, if your program has type errors at compile time, it will also have type errors at runtime. The difference is when you know about them.

Static type checking is (informally) about statically proving that the domain of your function is actually what you think it is. They catch "obvious" (for a definition of obvious that is defined by the power of the type system) domain errors. They don't replace tests, but they do constrain the domain you need to test and let you know when your tests are sane.


> And yet, in many cases they are a productivity drain. Time is often better spend by writing testcases instead of chasing down type issues(more so if a language has a 'generics' or C++ templates concept), so that approach got favored in many fields.

From my experience I've seen that type hinting and checking provides a better ROI than automated testing. It is not either/or, you will most likely need some automated testing, even if it is just unit tests. But at the very least use types. It provides broader benefits than just catching stupid bugs and typos. At makes your code base much easier to understand and code paths much easier to follow.


With static typing, you usually have this big suite of unit tests that check a whole bunch of basic correctness out of the box, called the compiler... /s

If you actually use the type system, and build less anemic types, you can eliminate huge swathes of potential errors.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

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

Search: