
The Unreasonable Effectiveness of Dynamic Typing for Practical Programs [video] - tosh
https://www.infoq.com/presentations/dynamic-static-typing
======
falcolas
Attempting to argue about the value of dynamic typing right now is like trying
to win the vim/emacs (or is that now just Sublime Text/Atom) editor wars. It's
more a recitation of dogma than useful conversations; you can't discuss any
type system's pitfalls without "oh, that's just algebraic typing, not static
typing".

Fear not, my fellow Ruby/Python/Lisp/Perlers - the pendulum will swing back,
and all that will be discussed is how much of a pain the current static typing
systems are and how type hinting is the real future.

~~~
abetusk
I get the feeling that you didn't watch the talk. He specifically talks about
the field making large decisions based mostly on dogma and that evidence
should be used to make those decisions instead. He's trying to advocate the
use of the scientific method as applied to language design choices.

He admits that the papers he cites are too few to make large decisions and
that the analysis he does by looking at how many issues on GitHub are actually
caused by type system errors (1-5%) but he's trying to steer away from
dogmatism in favor of empirical evidence.

------
michaelfeathers
At the same time that this is showing on HN there is an article about an ESPN
Analyst walking away after being upset at trauma on a football field [1]. The
comments are very interesting in that they point out that football helmets may
actually exacerbate injury because they their protection leads to players
feeling protected enough to risk harder hits.

I don't think we have that same effect in software but I think there is an
related one: when you don't have the protection of a static type system, you
may become more careful.

[1]
[https://news.ycombinator.com/item?id=15141495](https://news.ycombinator.com/item?id=15141495)

~~~
sidlls
I find the opposite happens. Without the protection of the compiler enforcing
rules before execution can occur we get "engineers" writing a mountain of
fragile, dynamically typed mess quasi-protected by untold numbers of unit
tests (themselves fraught with peril) half of which implement (poorly) type
checking.

~~~
l0b0
Ditto. After learning Java at uni I harboured a dislike for it for over 10
years while using dynamic languages. Now, having used it to implement
microservices in an actually sane development environment, I love it to bits.
Static typing gets rid of a crap-ton of subtly broken code which can only be
guarded against with a ridiculous amount of over-testing. You basically need
800% coverage to handle all the special input and output which is handled in
frankly insane ways. For example, how many methods treat zero as falsy (`if
index`) and how many do the opposite (`if index != false`)?

------
KillSticky
The presenter focuses on the cost of building software in the first place.
Yet, in practice, the cost of maintenance often dwarfs the cost of initial
development. This is where statically typed languages hold major advantage of
dynamically type ones. In part, thanks to the ease of refactoring with modern
IDEs.

The presented studies can hardly be used as a basis for making his arguments.
The study where a student invests a new language that nobody else could
possibly know is especially bad. Of course the learning curve is going to be
longer for an invented statically typed language! You cannot assess
productivity without factoring out the initial learning time. If anything,
that study only shows that dynamically typed languages are easier to get into.

~~~
flavio81
> Yet, in practice, the cost of maintenance often dwarfs the cost of initial
> development.

However, funding the development is sometimes harder than funding the
maintenance: You need the system to operate in the first place in order to get
the benefits/money/revenue/etc.

Once the system is in place and brings benefits, it is easier to fund further
investment on the system, be it maintenance, extension,etc.

~~~
KillSticky
The initial development of a chunk of code is not the same as the initial
development of a shippable product. Code maintenance starts the moment you
need to go back and add anything nontrivial to the existing code. And you will
have to go back and change your code many times while using your initial
funding. So the product funding argument, while true, does not apply here.

Try reading your own code written two weeks ago. How about a month ago? Three
month ago?

Let's say your code is making some calls and gets back an object. Can you list
all the important methods on that object from the top of your head? With a
statically typed language IDE can always do that for you. If your team member
added some more methods, they will be listed too. No need to waste time
reading the function source to figure out the return type, and then waste more
reading the class's source.

~~~
flavio81
> Try reading your own code written two weeks ago. How about a month ago?
> Three month ago?

In fact i did. Code of 8 months ago (december 2017). Easy to read, because i
used named parameters extensively and added comments at every function
Signature. Language Python (dynamic).

Don't assume that all programmers can't understand their past code. All my
peers (we have over 12 years doing this professionally) would do the same. It
is part of our job to read code written even years before by us. You never
know when a past customer (internal or external) will call you to want an
enhancement or upgrade.

> With a statically typed language IDE can always do that for you.

So? Not everybody needs to use a crutch to walk. How about having to patch a
_live_ code on a running server using only a plain text editor? This is not
fiction, this is real. (Servers almost never have any kind of IDE or even
fancy text editor installed).

> And you will have to go back and change your code many times while using
> your initial funding.

I am speaking about production software for using within an enterprise
context. You have a defined budget and you need to ship a working product. For
us , "maintenance" phase starts after the product has gone live in production.
For us, if we deploy a working product, it is easy to get additional funding
for maintenance and improvement. If we don't, we don't get subsequent funding
for anything. Business will value more a slightly buggy program that is ready
in 3 months than a perfect program that only is deployed after 8.

~~~
KillSticky
> So? Not everybody needs to use a crutch to walk. How about having to patch a
> live code on a running server using only a plain text editor? This is not
> fiction, this is real.

This is not a sound argument to advocate dynamic languages. There are good
reasons industry best practices include things like code reviews, testing, and
staging environments. But I suppose, if you are going to ignore all of these,
then the lack of type safety is the least of your concerns.

~~~
flavio81
Your argument was that some IDE features are really really needed.

> There are good reasons industry best practices include things like code
> reviews, testing, and staging environments. But I suppose, if you are going
> to ignore all of these, then the lack of type safety is the least of your
> concerns.

You are doing a strawman fallacy. I _never ever_ spoke about having or not
having code reviews, and separate testing and staging environments. All of
these are also necessary, and what in the world does this has to do with
static typing? Testing and staging environments are required and doable using
any programming language.

------
galaxyLogic
I think one needs to be clear about the difference between "typing" and "type-
checking". Typing means that objects can be instances of one or more type.
Type-checking means that we can annotate the code so that it contains
assertions about the types of its objects.

Static type declarations can help program understanding a lot without no
effect on performance. But on the other dynamic assertions of properties of
objects can be more flexible, you can program your own type-checking
algorithms and define your own types and what it means to be a member of them.

------
erokar
Like Smallshire mentions in the talk I think the way forward is gradual
typing, like you have in e.g. TypeScript. Best of both worlds.

------
ivan_ah
I agree with the speaker that adding basic static types (Java/C/C++) is lot of
work for not a lot of benefit.

However advanced type systems like the one in Elm, now that's something else.
Especially when refactoring and almost-writes-it-for-quality error messages
from the compiler.

------
preordained
The thing I find interesting is that in many cases the types are "there",
dynamic or not, except in most statically typed languages you have to type
them out for the compiler. If explicit types everywhere arent an awesome
communication tool, then shouldn't we be able to do away with them and keep
the benefits? Or 85%? 90? Why not an uncluttered lisp or Python syntax and
tools that don't require us to spell out the types by keyboard?

------
cwyers
If one doesn't have an hour to watch this (or wants to know if spending an
hour watching this is worthwhile), is there a summary somewhere of the points
this covers?

~~~
FridgeSeal
[https://news.ycombinator.com/item?id=15144544](https://news.ycombinator.com/item?id=15144544)

------
flavio81
When static typing vs dynamic typing is discussed, i see that often a mistake
is made and "static typing" is confused with "strong" typing, and "dynamic
typing" is confused with "weak typing".

Just FYI: If one wants his/her "programming language" to catch typing
errors/violations/mismatches, one needs to use a language with _strong typing_
, regardless of if the language itself supports static or dynamic typing.

Dynamic typing relies on being able to infer the types; this can be often
achieved at compile time but in any case at runtime, if the platform is
_strongly typed_ , any type error will be caught. Then, after being caught,
what could be done about it depends on your platform -- some platforms will
die with an error; some others will die but allow you to inspect what
happened; then others allow you to patch (correct) the error and continue.

Static typing requests you to explicitly declare types. This helps the
compiler do as much checking as possible at compile-time. This has the added
side benefit to increase the speed of the compile code, since less checking is
needed at runtime.

Some systems are dynamically typed but weakly-typed. Examples are PHP and
ES/Javascript.

Some systems are statically typed but fairly weakly-typed. The
Kerninghan&Ritchie C language is an example.

Some systems are dynamically typed and strongly typed. Python is one of them,
although it also has what is so-called "duck" typing.

Some systems support dynamic _and_ (optional) static typing, and are strongly
typed. Common Lisp is an example, as well as in Julia. In both cases (CL and
Julia), static typing helps for increasing performance, and for achieving
multiple dispatch (i.e. for Object-Oriented programming). CL is able to do
compile-time checking as well, if the implementation supports it.

Some systems have fairly complete strong typing which relies on extensive
static typing facilities. Haskell is an example.

~~~
rkido
> Static typing requests you to explicitly declare types.

You are confusing _explicit typing_ with _static typing_. "Static" means
"compile-time checking". "Dynamic" means "defer checks to run-time". Some
language implementations can disable run-time type checking if optional type
annotations are added, such as in Typed Racket.

In other words, all the following combinations are possible:

    
    
      - Implicit + dynamic + strong: Python
      - Implicit + dynamic + weak: JavaScript
      - Implicit + static + strong: Standard ML without type annotations
      - Explicit + dynamic + strong: Elixir with type annotations
      - Explicit + static + strong: Java
      - Explicit + static + weak: C
    

I struggle to come up with examples for these...

    
    
      - Implicit + static + weak
      - Explicit + dynamic + weak
    

... but I'm sure they exist, perhaps as historical languages from when type
systems were less sophisticated. After all, "strong" and "weak" are entirely
relative, constantly in flux, and exist on a continuum. In a couple decades,
anything lacking dependent types might perhaps be considered "weakly typed".

~~~
flavio81
> "Static" means "compile-time checking". "Dynamic" means "defer checks to
> run-time"

It is not really about _checking_ , it's about _allocation /binding_. Static
typing, for binding a variable, requires declaring the type of the data, this
can be explicit (as in C++) or inferred from another value where the type was
declared (as in C++ with 'auto' or as in Haskell).

Dynamic typing does not require a type declaration. You can bind an unassigned
variable and then assign a value later, without having to declare the variable
type.

~~~
rkido
I'm no expert on type systems so my definitions are not meant to be
theoretically correct, just to describe the practical implications of static
and dynamic typing.

What you're talking about seems to be more related to how type systems are
implemented, i.e. that dynamically-typed languages are "unityped", such as the
one and only "static type" of Python being the PyObject. Thus, when you "bind
an unassigned variable and then assign a value later", you're actually
implicitly declaring the type to be PyObject.

But it is deceptive to define type systems purely in terms of this
implementation detail, because then it would appear as if dynamically-typed
languages are _untyped_ (i.e. "everything is one type" is the same as saying
"there are no types"). Since we know languages like Python do some type-
checking at run-time despite everything being a PyObject, it is more useful, I
think, to think of these languages in terms of the practical result rather
than in terms of the implementation.

But if that's not what you're saying, I apologize.

------
s17n
This video is not worth 50 minutes.

tl;dr:

A bunch of stuff about typing that you probably already know and for which
there are better resources.

He cites a couple studies showing that static typing doesn't improve
productivity.

His own analysis of python projects on Github shows that only 3% of bugs are
caused by type errors.

He acknowledges that the evidence is not good and thinks that the industry
should invest in doing better studies.

------
saosebastiao
This is just rehashing the same tired and fallacious argument that most errors
aren't type errors. And it is pure fallacy because different languages have
different ideas as to what constitutes a type, and therefore have completely
different ideas as to what a type error is. If I write `Object.new.foo` in
Ruby, I get a NoMethodError. Not a type error, right? But that's the thing,
that would be a type error in almost all statically typed languages.

And it doesn't even come close to stopping there...there are hundreds of
examples of errors that are not Type Errors in your dynamic language that
_are_ type errors in some other language. NullPointerException? That's a type
error that is entirely prevented by _many_ type systems. If you accidentally
multiply your time and distance instead of divide to get your velocity, that
would be a Type Error in F# using their units of measure types. If you
accidentally pop an empty stack, that might be an EmptyStackException even in
many statically typed languages, but would absolutely be a Type Error in a
language with dependent types like ATS or Idris.

In the end, dynamic typing is just a way to make runtimes slow and building
correct programs slower, in exchange for some entirely subjective aesthetic or
ergonomic quality. And sure, most type systems in practical languages aren't
"complete" enough to eliminate the need for tests. But don't tell me that
because you don't eliminate tests that you don't need types. That's bullshit.
Statically enforced types eliminate errors, and every check that you defer to
runtime is an error waiting to happen. If you're willing to make that
tradeoff, by all means do so...but don't lie to me.

~~~
evincarofautumn
Exactly. Almost any error can be a type error with a sufficiently expressive
type system, because a type system is just a way to mechanically prove things
about your code. A more expressive type system lets you prove more useful
things.

Of course, you can easily do things in a dynamic language that are
_observably_ correct, but which are very difficult to _prove_ correct without
advanced logic (i.e., an expressive type system). And more advanced type
systems typically lose some nice ergonomic properties of simpler type systems,
such as clear error messages, type inference, principal types, or
decidability. So I can definitely see how someone moving for example from Java
to Python would find the latter very liberating at first.

On the other hand, you can argue that people shouldn’t be writing code that’s
so difficult to prove things about in the first place. :)

To my mind, the benefit of static types is that they _allow_ proving
properties of your code, but the cost is that they may _force_ you to do so;
the benefit of dynamic types is that they _don’t force_ you, but the cost is
that they _don’t allow_ it.

On balance I think static types are therefore preferable, because you can
always fall back to dynamic types (strings/hashes/variants/sums) in a static
language, but you can’t “fall forward” to static types in a dynamic language.

~~~
junke
> ...the cost is that they don’t allow it.

Saying they don't allow it is too strong (they allow it, but it might be
harder).

~~~
evincarofautumn
You can’t prove even very simple universal properties of dynamically typed
code, such as “when given an integer, this function always returns an
integer”, because its result type can depend on the _value_ of the integer
passed in at runtime. You can only prove existential properties (“there are
integers for which this function returns an integer”) through testing.

That testing can be done before runtime in some languages, e.g., with Perl’s
BEGIN/UNITCHECK/CHECK/INIT blocks. But it’s not really the same.

In a referentially transparent, statically typed language, if you give me a
function of type ∀a. a → a, then I know for a fact that if this function
halts, it must be the identity function. The only thing it can do is return
its input.

The same function in a dynamically typed language could return _any value_ of
_any type_ , because its result’s type can depend on both the type and the
runtime value of its input, or not depend on its input at all, even if it’s
referentially transparent.

Even if you supply that type annotation and check it at runtime (“this
function returns a value of the same type as its input”), it would only be
checking a single code path, and it could still give me any value. That is, a
valid implementation would be “return 3”—I wouldn’t get a type error if I only
ever called it on integers, but it would still be non-parametric.

~~~
junke
"when given an integer, this function always returns an integer"

    
    
        (defun foo (x)
          (declare (integer x))
          (round (/ x)))
    
        (describe #'foo)
    

Derived type:

    
    
        (function (integer) (values integer rational &optional))
    

When given an integer, the function returns an integer, as well as an
additional rational value. ROUND's specification says the type of the
remainder is a REAL, but since I call it with integer, it is more precisely a
RATIONAL (a subtype of REAL).

    
    
        (foo 0)
        => arithmetic error DIVISION-BY-ZERO signalled
    

Note, the meaning of the derived type is: _if the code returns a value_ , then
its type is .... The same applies in OCaml, where you can raise exceptions.

Let's restrict the input domain to non-zero integers:

    
    
        (defun foo (x)
          (declare (type (or (integer 1 *)
                             (integer * -1)) x))
          (round (/ x)))
    
        (describe #'foo)
    
        Derived type:
    
        (function ((or (integer 1) (integer * -1)))
                  (values (integer -1 1) (rational -2 2) &optional))
    

The primary return value is an integer between -1 and 1; the remainder is a
rational between -2 and 2 (ranges are inclusive).

~~~
evincarofautumn
Cool—Common Lisp? Isn’t that a promise to the compiler that you will obey the
type, not a request to the compiler to enforce that you do? And can it
correctly enforce something like the (∀a. a → a) that I described?

I consider gradual typing (e.g. Typed Racket, Hack) an interesting case of
static typing where you’re trying to provide good interop with your
dynamically typed surroundings, typically while migrating from dynamic to
static types. Not all dynamically typed systems that allow type annotations
have “gradual typing” in that sense, because many of them don’t give you
enough power to get to “fully annotated” code (where the compiler is finally
free to erase types completely).

~~~
kazinator
That's right; those are promises to the compiler whose main use case is local
"hotspot" optimizations. Those promises can be used as checking assertions in
safe code; they become dangerous if code is optimized with _safety 0_.

You'd work around not being able to express (∀a. a → a) by focusing on the
specific use scenario in the optimized code where that function is being
called, and the concrete types that are involved. The _identity_ function fits
this type. If we know that _x_ is _fixnum_ , we can do this: _(the fixnum
(identity x))_. Using _the_ , we an assert types of individual forms.

Which is not to say that an implementation cannot do type inference for
(identity x) and represent it type as (∀a. a → a) ; it's just that type
inference is a separate thing from the ANSI CL declaration system.

