What is the appeal of dynamically-typed languages?
gist.github.com
gist.github.com
Yes, the mostly-manifest, nominal-oriented type systems of PLs like Java and C++ can be wearying, and something like Python feels like a breath of fresh air in comparison. However, a more inferred/structural approach is possible in a statically typed PL, as Haskell demonstrates so well.
Like Python, Haskell does not require you do "assert that x is a Collator" before doing some collation. It tends not to lead to complex hierarchies. And its standard containers are versatile and well supported.
The point here is that most of the issues discussed are not really issues with static vs. dynamic typing. They have rather more to do with manifest/nominal vs. inferred/structural typing, along with how much attention the PL designers gave to minimizing overhead & boilerplate.
The essay I really want to read is "Why I Prefer Python to Haskell" -- particularly if it is written by someone who actually gets Haskell.
I guess a lot of it may be a mindset issue - but the way the python forces simplicity is quite appealing to me. I've heard similar thoughts from people who like Go.
'Good' refactoring in Pythonland tends to mean often splitting things into smaller functions.
In Haskell, it can mean making functions 'points free', which is clever, can result in very nice looking one-line functions, which are totally incomprehensible unless you know exactly what types all the functions involved take and return. Or moving the logic out into combinators or higher order functions which can be used in many places, but become increasingly abstract in name and in variables.
Partial application & currying are amazing, and often results in very elegant solutions - but again, unless you know beforehand how many arguments a function takes, it's easy to miss that someone is talking about a curried function, and not the result of said function.
The crazy math-oriented naming of variables by single letters rather than by 'accumulator' or 'first_input' or whatever, especially in tutorials, API documentation, etc. also require an extra mental effort each time. It reminds me a bit of perl, in that way. Just because you can write code as a one-liner, doesn't mean it's always a good idea. Python refuses to let you write code as a one-liner (beyond very simple things), and so the tendency to name things extremely laconically is reduced. Pylint and other tools even throw warnings when you try to use single-letter variable names.
The argument always is, 'You can write complex & hard to understand code in any language!'. Yes. That's true. Of course. But also, languages are more than just the syntax, the culture and available libraries & documentation are quite a big thing too.
The direction of Haskell code is, at least in my feeling, towards increasingly abstract concepts. The direction of Python code is, IMHO, towards simplicity.
Another, perhaps related issue is Exceptions. In simple scripting / basic applications, you could have a whole bunch of different exceptions raised, and honestly, you don't care which one.
For instance, saving a file. If there's a permission error, out of HD space, invalid filename for this disk format, network timeout on NFS, restrictions on what kind of data can be put in this filetype, whatever. Some of these may be IOErrors, some may be json encoding errors, or whatever. Often, I don't care which it is, I just want to throw up an error message saying, "Something went wrong: %s." % err.msg. Being able to ignore exceptions in the small, write very simple & concise code that can raise many different kinds of exceptions, and only catch them further up where it makes sense can often mean I can lump many unrelated types of exceptions together by what area of the application they're part of.
I feel like Python is moving in the direction of a better compile-time experience with the new type annotation stuff in the latest 3.x version. I really miss that security when working in Python.
The string conversions lib is a great example. The function `cs` has about 20 definitions based on different types of input and output. Instead of being named TextToString or BytesToText, etc. There is just one function `cs` that can be used anywhere and everywhere. Supporting multiple input types is nothing special, but by inferring the output type from the context of the call you, as a developer, can safely ignore types.
That was (and is) a difficult concept to wrap my head around. I do, however, find myself paying less attention to types until the compiler informs me I have a mismatch.
It tends not to lead to hierarchies at all, with the whole subtype polymorphism thing not being supported.
> They have rather more to do with manifest/nominal vs. inferred/structural typing
Type inference can be supported in nominal type systems, it just hasn't been explored as much; and there are languages like scala that at least do local type inference while having nominal constructs.
That's the kind of thing you do in C. For instance Unix: dup, dup2. (Linux adds dup3!)
That doesn't pass the "smell test", relative to all the claims about how Haskell and its ilk are the most amazing thing since sliced bread.
"In an interesting way, it keeps you from going all-in on object-oriented programming. You only want to create a class when a casual API user will understand how it works and what they would use it for. "
Completely disagree. Python doesn't discourage OO at all. I think the author has a particular programming style with Python that does not line up with the way many people I know use it, and this may give the wrong impressions. Yes, you can just pass around tuples, but ... don't do this :) It has quite workable classes.
So, answering the question? What's the advantage? Dynamic language does require, to a degree, a small amount more tests. However, type errors are also a class of error that is very very infrequently encountered as to a problem.
I'd say that duck typing principly allowers easier mock interfaces and ability to more easily write generic-type functions, and for unrelated reasons, such languages have a very quick code-test loop. But their efficiency in this isn't so much to do with typing. It's mostly the speed of this loop, stemming from a lack of compilation, that helps. Those are not really related, but that's why people like dynamic languages.
They have also been historically more expressive, and saved typing things, for when type errors aren't really common (for me, they aren't), always specifying types doesn't always help. But to be honest, I don't mind it and it's nice to have as documentation most of the time.
I do mind compilation cycles.
The article also seems to err in that it assumes there can't be debuggers available for typed languages, where it says "you can't see inside the process".
The argument that people like dynamic systems because they build programs out of trial and error seems erroneous in all. I build my programs out of tests. This can still happen just the same and has nothing to do with a type system.
As someone working every day in Python, this seems mind-bogglingly divorced from reality.
I don't know if you mean something far more restricted by "type error" but most of the errors I encounter - in my code, the code of my team, and the code of those on other teams, across several projects - are errors that would have been caught by the type checker in Haskell had I been writing similar code. Many of them would have been caught by the type checker in C.
I think some types of developers are more likely to make type error mistakes, and it's likely I make other mistakes, sure. I've done a wide about of massively-open source projects, and it doesn't really come up. The times I pass the wrong object type simply doesn't happen.
Do you use keyword arguments? These help a TON with argument order, and may be why you are having that problem.
Maybe you don't "see" type errors because you consider them to be failures of test coverage, or you say, "That's not a bug, it's more of a typo?" Or you don't work on a shared codebase?
My statement was about the claim and about me - I meant to express (colorfully) the distance between the claim and my experience. I did not intend to cast aspersions on the claimant; I certainly apologize if I caused offense, though you seem to have taken it in good spirits regardless :)
Back to the topic at hand:
It sounds like you probably do consider a substantially narrower class of things "type errors" than I do. It is quite rare that I mix up arguments to a function. More commonly I see bad assumptions about the contents of a map, which is often a type error. The most common form of exception I see in Sentry seems to be "NoneType object has no attribute...", which is always a type error.
GvR supports optional type checking in Python and has written a few documents laying out its defense, so it is in the works.
Many programmers who are only used to dynamic languages think it means something like the following in Java: "String foo = 5;".
But when you start learning something like Haskell you start noticing that many of the things you took for granted as "problems that just happen when you do programming" can actually be found at compile time and they will start frustrating you every time you see them.