
Seven deadly sins of talking about “types” (2014) - porker
http://www.cl.cam.ac.uk/~srk31/blog/2014/10/07/#seven-type-sins
======
_bxg1
Good points. However, I disagree with this:

> But please, stop pretending that type-level programming is a desirable
> thing. It's a kludge, and hopefully a temporary one. Personally, I want to
> write all my specifications in more-or-less the same language I use to write
> ordinary code. It's the tool's job to prove correctness or, if it must, tell
> me that it failed.

I don't see explicit type-programming as (always) a kludge; oftentimes it's
the primary vocabulary for expressing certain ideas. I.e. type annotations are
not (always) just typechecker-hints, but can be explicit documentation of
intent.

> We need to be able to step up the strength of our specifications without
> switching language all the time. We need to integrate typed and untyped code

This has been a revelation during my time spent with TypeScript. The fact that
I can just add a comment that disables an unhelpful type error on a given line
is a game-changer. Or that I can assert to the system "no really, this value
that has a union type is actually just this particular subset of that union"
(cast) and retain meaningful checks after that point. Having a type system
that's _flexible_ has made it enormously more useful. It allows you to deal
with those cases where what you want to do can't be type-checked, within the
context of (and playing nicely with) a broader system that _is_ type-checked.

~~~
atoav
As somebody who moved from untyped to typed (Python to Rust) I think the two
biggest arguments for typed systems are:

\- they encourage you to keep you code data oriented, which is usually (at
least in my experience) a food thing

\- certain classes of mistakes can be checked for before you even run the code
itself

~~~
quickthrower2
They also make refactoring much easier. Renaming a function or variable and
having all "true" references renamed (not just things with the same name but
in a different scope), for example.

~~~
polityagent
Safely and automatically renaming a function, local or global variable does
not require a type system (see clojure and many other dynamic languages). Only
renaming an instance field on a class needs it (because then all accesses on
an object must be renamed, which requires knowledge of the type of your
variables).

Incidentally programming in a more functional style reduces the need for this
kind of thing. it doesn't eliminate it though so it can still be a source of
runtime bugs when typos creep in, that's a trade off you have to make.

~~~
polityagent
Unit tests for business logic and specifications for data shape can help to
catch those typos as a side effect of the other benefits they bring. However
typo catching will never be as solid as in a typed language, I've decided I'm
ok with that as long as I'm not programming something that could kill someonev
if it crashes.

~~~
tsimionescu
If you're writing an application that could kill someone if it crashes, you'd
better not be relying on the type system to catch those crashes for you, since
it will only catch the most obvious ones.

For any life-critical system you want first and foremost to have a battery of
end-to-end tests that run the software on the target hardware under various
operating conditions. No amount of software-only testing/formal methods will
give you this part.

Then, you want to have a formal specification of your program that can be run
through a model checker - this would be the industry standard, though, if you
have a team of PhDs at your disposal, you could also try to go with full
formal verification a la Coq.

Finally, you want unit testing, integration testing, and possibly a type
system to help day-to-day development. But do note, none of these should be
critical for assuring the system works as indended - if typos in your code
aren't caught by your end-to-end tests, what hope is there that they will
catch complex dynamic behaviors?

~~~
speedplane
> If you're writing an application that could kill someone if it crashes,
> you'd better not be relying on the type system to catch those crashes for
> you, since it will only catch the most obvious ones.

Safety critical systems are quite different from the vast majority of software
systems, and raising them as an example in a general programming argument
isn't really fair. There already are safety critical certifications that are
mandatory for many aviation and healthcare related applications. They
generally utilize heavy testing, model checking, and supporting documentation.
Very few commercial industries operate with this much attention to quality,
basically because it's too expensive.

~~~
tsimionescu
The GP had raised them, that's the only reason I commented about this.

You are right though, the standard for that type of software is very different
from regular programming.

Still, we shouldn't forget that even the strongest typing can't substitute
testing. Most likely, even if you had a formally proved system, which is
already much, much more difficult than any other kind of regular software
development practice, you would still want some amount of testing to go with
that system before putting it in production.

------
choeger
The author is right with every. Single. Point.

 _But_ I think their criticism comes from an ivory tower where people in
general agree that efficient programming needs strong and sound tools.

Unfortunately, in practice it seems to be all about "velocity". When you see a
huge codebase in python grown over several years and try to refactor it,
reason about it, or just enhance it, you will inevitably come to the
conclusion that the omission of static type checking is a grave error from a
system-design perspective. And when you work with the developers of such a
code base you will understand that static type checking is first and foremost
a means of communication between the language implementation and the
developers. Without this tool it is like taking away a common spoken language
between the developers and having to translate everything into some form of
pidgin.

~~~
pytester
>When you see a huge codebase in python grown over several years and try to
refactor it, reason about it, or just enhance it, you will inevitably come to
the conclusion that the omission of static type checking is a grave error from
a system-design perspective.

I inevitably came the exact opposite conclusion. Perhaps because at the start
of my career I was trying to do the same thing with Java, and it added
overhead and expense without a commensurate payoff in safety.

Frankly, I think velocity is ridiculously underrated:

* Because of the age old "customer doesn't know what they want" issue - hence you throw away a lot of the code that was not fit for purpose.

* Also because the right kind of architecture is only obvious in retrospect - hence you throw away a lot of the code that was not fit for purpose.

* And finally, because the only times in my career where I've had significant success in refactoring a large mission critical codebase it was python and it was because I'd managed to _quickly_ build custom tooling infrastructure (e.g. testing) to support that refactoring.

I do take some issue with python's type system (e.g. the whole truthiness
thing), but on the whole it balances safety and velocity in a highly pragmatic
manner.

~~~
cjfd
Well, java tends to be one extreme and python the other.

Java appears to be the most bureaucratic language around where most code is
written by people who are just a bit too enthusiastic about OO so every class
becomes a FactoryManagerPatternVisitorImplementation and every struct 'of
course' needs getters and setters.

Python on the other hand is often written without any safety and I have
frequently witnessed how every typo becomes a new variable maybe just because
the cat stepped on the backspace key and how one developer changes the
signature of a function but not all places where it is called.

I think there are great benefits to compile/lint time type checking for
anything bigger than just a throwaway script. For python one can use mypy,
which helps. There is no need to, though, to turn everything into a festival
of object orientedness. Most classes need neither parent nor child. And some
functions can just be global functions.

~~~
pytester
I think a lot of java's "bureaucracy" is simply duct tape around its badly
constructed type system.

>Python on the other hand is often written without any safety

Oh for sure, but I find it _easy_ to start dialing up python's safety from day
one of starting on a project (e.g. start writing asserts), no matter how
complicated.

Working around java's horrible type system? Less easy.

------
andybak
> In a typed language, only polymorphism which can be proved correct is
> admissible. In an untyped language, arbitrarily complex polymorphism is
> expressible. [Hickey's tranducers give an] example of how patterns of
> polymorphism which humans find comprehensible can easily become extremely
> hard to capture precisely in a logic. Usually they are captured only
> overapproximately, which, in turn, yields an inexpressive proof system. The
> end result is that some manifestly correct code does not type-check.

There is something here close to a feeling that I've got that I struggle to
experess. My two day to day languages are Python and C#. My C# code is usally
longer and more complex than my Python code. At least part of this isn't down
to the problem domain, syntax or features available in one but not the other.
However I struggle to put into words exactly why or what is the cause of this
(or even to really quantify the difference as I'm doing very different things
in each language).

The pendulum has swung massively towards pro-static-typing but I remember the
last cycle and I would like to hear from more people who were around for the
last time we all started to decide static typing had huge drawbacks and the
cost of dynamic typing was a price worth paying.

~~~
Smaug123
Well, C# is a pretty verbose language and it really pushes you towards object
orientation (which is itself a verbose paradigm). It's not surprising that
your code is longer in C#. I'd say a more fair comparison with Python would be
F#, which is roughly as terse as Python.

------
GnarfGnarf
I've written a lot of C++, and a fair bit of Python. Dynamic typing is
seductive at first, but you eventually realize that you would much prefer
errors be caught at compile time, rather than run time.

I want a language where I can:

    
    
        cInches in1, in2, in3;
        cCm cm1;
    
        in1 = in2 + in3;  // OK
        in1 = cm1;  // compilation error: cannot mix units
        in1 = in2 * in3;  // compilation error: square inches are not inches
        in1 = 2.0 * in2;  // OK
        float f1 = in1 / in2;  // OK
    
        cCm foo(cInches in)
        {    return cCm(in * 2.54);
        }
    
        foo(cm1);  // compilation error
    

Naturally this is accomplished with special classes (not shown here), it is
not an inherent property of the language.

~~~
clarry
Why do you want these compilation errors?

Both are measures of length. I can add an inch and a centimeter, and the
result is a well defined length that we can express in inches, centimeters,
light years, or any other measure of length. Likewise for areas or ratios of
lengths.

I would generally prefer it if the language just gave me the correct result,
which I can then print out in the user's preferred unit.

~~~
GnarfGnarf
Adding inches and raw centimetres is like adding apples and oranges, which
they taught us not to do in grade school.

What you are thinking of is:

in1 = cm1.toInches();

If someone passes cm to a function that expects inches, much error and grief
will result.

~~~
clarry
No, I'm not thinking of

    
    
       dec1 = hex1.toDecimal();
    

because

    
    
       num1 = 0xff00 + 64;
    

works just as well and so does

    
    
       functionThatExpectsNumber(0b1101);
    

as does

    
    
       p1 = mat1 * vec1;
    

Lengths are just as absolute as integers, but we have different ways to input
and represent them. A sufficiently advanced language understands the
equivalences and does just the right thing. Adding apples and oranges does not
happen.

Generally your function should not expect inches, it should expect some length
and I can input it in any unit of length I want.

Sure there are cases where you're constrained and need to account for the
limited range and precision of your underlying storage format, but that should
not be the default for any modern high level general purpose programming
language. We don't need to concern ourselves with grade school stuff, the
computer can do it for us.

Adding apples to oranges is concern at a lower level than the abstraction we
should be working with by default. Doing arithmetic by hand on pen and paper
is pretty low level.

Adding apples to oranges is also a concern when you don't have proper types by
which the system can deduce that orange is actually just 2.54*apple.

~~~
GnarfGnarf
You're missing the point. I make an app that prints charts. It includes a
function that subtracts user-specified margins from the paper width, to
calculate available space. Some of my customers are in the U.S (inches),
others are not (cm).

If I call

    
    
        printPage(0.75, 0.75);
    

you really think it's going to be all the same whether "0.75" represents
inches or centimetres?

------
js8
The problem is, just like with "function", the usage word of the word "type"
is a bit less formal in programming than mathematics.

As far as I can tell, we use "types" in programming for 4 things:

1\. Modelling the domain (e.g. type Temperature)

2\. Limiting the range of values (e.g. enum)

3\. Decision about how to store the data (e.g. ArrayList)

4\. Specification of dispatch on values (e.g. file as an interface)

The formal type theory deals with 1 and perhaps a bit of 2. The original usage
of types in programming (in old assemblers, Cobol, etc.) was 3. Dynamic
programming languages primarily use the term in the 4th meaning.

~~~
nbevans
Your #2 is wrong (sorry). Enums in most mainstream languages don't "limit";
they merely specify known values. Unknown values are still possible. For a
type-safe enum, see discriminated union.

~~~
thethirdone
Can you give some examples other than C, C++, and C# where this is true? Most
languages either don't have them as definable types, are C based and therefore
map them to integers, or actually limit values.

AFAIK, python, java, rust, ocaml, haskell, and swift all have enums that
actually limit values.

~~~
t-writescode
You can do bitwise operations in Java

[https://stackoverflow.com/questions/6282619/is-there-a-
way-t...](https://stackoverflow.com/questions/6282619/is-there-a-way-to-
bitwise-or-enums-in-java)

------
cjfd
Mostly good points but also some quibles.

"Personally, I want to write all my specifications in more-or-less the same
language I use to write ordinary code."

If we take 'specifications' one level further and actually want our algorithms
to be proven correct, as in, by a proof assistant it is quite unclear that
this possible or desirable. Maybe the best way to program is to have
specifications in a language as close to mathematics as possible and
executable code in a language much closer to the machine. It is not clear to
me that both of these things should be the same language.

"Equivocating around type-safety"

The author seems to want 'type-safety' to mean that it is made sure that an
integer in memory is not accessed as if it were a float. While this is clearly
a desirable feature that also dynamically typed languages provide by checking
and/or converting at run time I really don't think this is the meaning of
'type-safety' in common parlance. If I attempt to use a variable as if it were
of a different type I generally get a runtime error in a dynamic language,
e.g., because I am accessing a field that does not exist. 'type-safety' is the
property that the language prevents this runtime error from happening because
this is checked at compile/lint time.

~~~
rntz
> If we take 'specifications' one level further and actually want our
> algorithms to be proven correct, as in, by a proof assistant it is quite
> unclear that this possible or desirable.

It is not only possible, it is how dependently-typed languages/proof-checkers
like Agda work.

~~~
cjfd
The question is whether the performance hit that one gets from running such a
language at run time is acceptable. For one, thing, it pretty much forces one
to use a purely functional language. Which may or may not be a good thing.

------
whateveracct
> Presenting type-level programming as a good thing

> Fetishizing Curry-Howard

These seem a bit causal :)

When you do closer and closer to dependently-typed programming (which
necessitates more type-level programming because dependent types aren't
present in any production language yet), Curry-Howard actually can be a nice
way to ground yourself when you get confused or stuck programming at the type
level.

------
lewisjoe
I like how the author acknowledges the problem with the very word "type" \-
[https://www.cs.tufts.edu/~nr/cs257/archive/stephen-
kell/in-s...](https://www.cs.tufts.edu/~nr/cs257/archive/stephen-kell/in-
search-of-types.pdf)

The word "Type" is such an overloaded term, just like how "Workflow" is an
overloaded term in the tech industry[1]. For example, when people say "X is
strongly typed language" means something much different than when people say
"In Haskell, function have types" [2] or "Idris has dependent types".

They aren't completely unrelated, but they mean two very different set of
things depending on the context.

[1]
[https://news.ycombinator.com/item?id=21544453](https://news.ycombinator.com/item?id=21544453)
[2] [http://learnyouahaskell.com/types-and-
typeclasses](http://learnyouahaskell.com/types-and-typeclasses)

------
arkanciscan
It's funny to me that someone can write so intelligently about types on a blog
that looks like such garbage on mobile.

------
waffletower
This blog post ought to circulate annually.

------
based2
\+ Avoid vagueness of 'type' as attribute.

------
Tomte
It makes me smile that blosxom is still in use.

