
What to Know Before Debating Type Systems (2008) - furcyd
https://cdsmith.wordpress.com/2011/01/09/an-old-article-i-wrote/
======
weinzierl
Chris Smith - who wrote the original piece years ago - added a current
prologue to the text. I think part of it is an excellent summary so I quote
this part here:

> If pressed for an answer, though, yes I still do believe in the central
> conclusions of the article. Namely:

> That “static typing” and “dynamic typing” are two concepts that are
> fundamentally unrelated to each other, and just happen to share a word.

> That “static types” are, at their core a tool for writing and maintaining
> computer-checked proofs about code

> That “dynamic types” are, at their core, there to make unit testing less
> tedious, and are a tool for finding bugs.

> That the two are related in the way I outline: that one establishes lower
> bounds on correctness of code, while the other establishes upper bounds, and
> that questions about their real world use should come down to the
> possibility and effectiveness of addressing certain kinds of bugs by either
> computer-checked proof, or testing.

EDIT: When I wrote this comment the post linked to Steve Klabnik's 'reprint'
of the temporary lost original article. Currently it links to the original.
The 'reprint' missed the prologue.

~~~
StefanKarpinski
Doesn’t hold water, imo. If dynamic and static typing were really independent
concepts, then why are languages only one or the other, never both? I
explained my take on dynamic versus static typing here:

[https://stackoverflow.com/a/28096079](https://stackoverflow.com/a/28096079)

> The primary difference between static and dynamic languages, in my view, is
> this: in static languages, expressions have types; in dynamic languages,
> values have types.

Read the rest of the answer for more details in the context of Julia, which,
as a dynamic language with a sophisticated type system, really crystallizes
the distinction.

~~~
thesz
Given algebraic type (as in Haskell - data List a = Nil | Cons a (List a)),
you can have static type verification (map :: (a -> b) -> List a -> List b)
and dynamic dispatching for situation in concrete part of a program:

    
    
      pairwiseReduce :: (a -> a -> a) -> List a -> List a
      pairwiseReduce f (Cons a1 (Const a2 as)) = Cons (f a1 a2) (pairwiseReduce f as)
      pairwiseReduce f as = as
    

We fix the type, but concrete instantiation of a type may vary in run-time, so
we pattern-match: the list can have at least two elements (first case) and we
reduce them and the rest of the list or it has less than two elements and we
leave it as it is.

I took my time and read about Julia's type system. It is really the worst from
both worlds: it does not allow for tagged unions (like List I declared above),
it does not allow for adhoc polymorphism (1+2 gives error for floating type
and evaluates for integers), there are no type classes (that adhoc
polymorphism thing) and, thus, no good guarantees for whatever substitution
for GADTs Julia has. And it also has inheritance which makes type inference
exponentially harder in simple cases.

So I urge you to take your time and read about Haskell's type system. It
really allows you to have static and dynamic types and vary their presence and
utility as you see fit.

~~~
KenoFischer
ML Style constructors are not particularly idiomatic, but you can write
basically the same thing in Julia:

    
    
        struct Cons{T, S}; head::T; tail::S end
        pairwiseReduction(f, a::Cons{T, Cons{S, Tail}}) where {T,S,Tail} = Cons(f(a.head, a.tail.head), a.tail.tail)
        pairwiseReduction(f, a) = a
    

([https://github.com/thautwarm/MLStyle.jl](https://github.com/thautwarm/MLStyle.jl)
if you want the syntax) but that's somewhat beside the point.

I think the primary problem here is one of terminology. Take Stefan's quote
from the post you're responding to:

> The primary difference between static and dynamic languages, in my view, is
> this: in static languages, expressions have types; in dynamic languages,
> values have types.

There is a clash in terminology here. People coming from a static languages
background would use the term "tag" for what dynamic languages people call a
type. There isn't really a super good word in the dynamic languages world for
what static languages people call a type, and the word type is generally re-
used, since the distinction isn't as important (generally there is an
embedding of the tags into the type system, so using "type" for both can be
sensible). I'm gonna adopt (my best interpretation of) the static languages
terminology here for this comment.

Some languages have both types and tags. The biggest difference then between
static and dynamic languages are which of the two is primarily dispositive (in
the sense of determining the semantics of the language). In dynamic languages,
the tags are, in static languages the types are. This choice has deep seated
implication for the kind of programs that can be written and the behavior of
the program.

In fact, you sometimes end up in situations where static languages have
multiple tag systems (but generally only one type system) and dynamic
languages have multiple type systems (but generally only one tag system). In
julia, the tag system is primary, but we have a very rich type system that is
embedded in the tag system by parametricity and is used for dispatch. This
type system is constructed in such a way that the subtyping relationship is
induced by tag system (for types T, S, (T <: S iff for all values v s.t.
tagof(v) <: T, tagof(v) <: S)) [1]. You basically want to choose a decidable
type system that satisfies this constraint, but is at the same time
sufficiently expressive to be useful for what people want to do.

We also have another, larger type system that is used for inference
judgements, but that is generally transparent to the user. It is in principle
possible to type check over any of these type static system, but type checking
is not part of the semantics of the language.

The problem with statements like "Julia's type system does not allow for X",
is that it presumes that the type system having some feature is a pre-
requisite for having the corresponding functionality. So while Julia's type
system does happen have support for unions (and universally qualified unions),
the significance of that is for dispatch, not for whether the language can
have expressions that can take on values of different tags.

In any case, I can assure you that many people working on Julia are well
familiar with Haskell's type system. Hopefully I was able to convince you that
there is at least something interesting about the way Julia's type system
works.

[1] I should credit the Northeastern PL group for the term "tag-based semantic
subtyping" here to describe this notion
([https://github.com/julbinb/ftfjp-2019/blob/master/paper/mini...](https://github.com/julbinb/ftfjp-2019/blob/master/paper/minijl-
ftfjp2019.pdf)).

------
apo
> Fallacy: Dynamically typed languages provide no way to find bugs

>

>A common argument leveled at dynamically typed languages is that failures
will occur for the customer, rather than the developer. The problem with this
argument is that it very rarely occurs in reality, so it’s not very
convincing. Programs written in dynamically typed languages don’t have far
higher defect rates than programs written in languages like C++ and Java.

Having written software for paid work in both Java (static typing) and
JavaScript (dynamic typing), I agree. A statically typed language does not
improve the quality of the software that gets written, assuming equal skill in
both languages.

The kinds of bugs that get caught by a static type system are also caught by a
good suite of automated tests, where every test is written _before_ the
production code that makes it pass.

The article doesn't mention refactoring, but this is where a static type
system really shines. The ability to refactor an entire code base with
confidence at the click of a button is extremely useful and the thing I miss
most when using dynamically typed languages. You can get some of that with
modern editors, but it's just a pale shadow of what you get with static
typing.

~~~
viraptor
> The kinds of bugs that get caught by a static type system are also caught by
> a good suite of automated

This is correct in theory, but to get the same benefit you'll need to write
_all_ the tests that the static language would guarantee. That's both relying
on the programmer being always correct and that they think the time writing
those tests is worth it.

~~~
arwhatever
Stating typing you have to explicitly opt out of, and automated tests you have
to go to quite some effort to explicitly opt into, and most teams I've been on
have been quite poor at this opting in.

------
dang
Posted at least 20 times but here are the discussions:

2015:
[https://news.ycombinator.com/item?id=9777485](https://news.ycombinator.com/item?id=9777485)

2011:
[https://news.ycombinator.com/item?id=2093926](https://news.ycombinator.com/item?id=2093926)

2009:
[https://news.ycombinator.com/item?id=849726](https://news.ycombinator.com/item?id=849726)

As for year, 2008 is an upper bound
([https://web.archive.org/web/20080802234250/http://www.pphsg....](https://web.archive.org/web/20080802234250/http://www.pphsg.org/cdsmith/types.html)),
so we'll put that above unless someone finds it earlier.

~~~
ryandvm
Super offtopic, but I'm curious why y'all haven't taken the dev time to
implement even primitive dupe detection/reposting/re-linking on HN.

Writing scripts to do repetitive human tasks is sort of hacker 101 isn't it?

~~~
ginnungagap
I suppose it's because reposting is not considered an issue if some time has
passed since the last submission

~~~
twic
Given that the same articles come up over and over again, and attract upvotes
and comments, it seems clear that the userbase gets value out of reposts.

Perhaps the HN software should turn the knob the other way, and automatically
repost old links which got a lot of attention previous times.

~~~
platz
I think I'm comfortable with the minimal amount of algorithm in my news feed,
and let humans drive the rest.

------
mercer
My background is php and then javascript. My first experience with 'types' was
learning that apparently javascript's implicit type conversion was not normal
and actually pretty bad. That made sense to me.

Then I learned about explicit types via TypeScript, and that made sense to me
too, although I could imagine how this could explicit typing could get in the
way of my prototyping, small projects, or REPL-driven development.

Now, a few years later, and having learned a bunch of new languages, both
explicitly and implicitly typed, I'm mostly wondering why I'd go for anything
that isn't _gradually_ typed as an ideal approach.

I get the impression that the core discussion is explicit vs implicit (correct
me if I'm wrong), and I can see how in some cases enforcing explicit typing is
worthwhile, but for most of the work I actually do, it seems like the best
solution is something gradual and flexible. Typescript, or perhaps some
(improved) version of Elixir/Erlang's Dialyzer, or Clojure's spec: flexible
enough to allow for said prototyping, small projects, and REPL-driven
development, but gradually rigid enough to be useful when things grow larger
and more solidified.

~~~
munificent
_> I'm mostly wondering why I'd go for anything that isn't gradually typed as
an ideal approach._

In some ways, gradual or optional types give you the best of both worlds. You
aren't obligated to submit to the whims of the type checker when you don't
want to, but you can elect to get the safety of types where it matters. You
can get the brevity of dynamic typing and the safety of static types.

But, in other ways, they give you the worst of both worlds. If you have any
sufficient fraction of typed code in your program, then you will feel pressure
to structure even the untyped parts of your program in ways that play nice
with static typing. Some API design choices are more "typable" than others,
and having static types at all discourages you from some interesting API
choices.

In order to play nice with untyped code, most gradual and optionally typed
languages are deliberately unsound. There are holes in the type system that
can't be closed. That means compilers cannot rely on types for optimization
purposes.

You end up with the rigidity of static types and the performance of dynamic
types.

~~~
revvx
I wonder if coming from the other way would be acceptable for fans of dynamic
typing.

Instead of adding types gradually to a dynamic language (like Typescript sorta
does with Javascript), we could add type-checking features that enable a kind
of "statically-checked duck typing". Complete inference (like Elm, Crystal or
Haskell) gets you halfway there – most of the time you don't have to write
method/function signatures.

Then add structural typing like Extensible Rows [1] or Elm Records [2], or
something that type-checks based on the structure instead of classes, and it
allows you to have the compile-time type-checking of Interfaces or
Generics/Templates without the extra typing. Then you can add explicit
generics/templates later if you want.

Both Crystal and Elm get very close to that for me, but not 100%. I honestly
think that this is a safer bet than gradual typing that could lead to very
similar results, but I don't know how feasible or acceptable to mainstream
programmers this is.

[1] [https://github.com/tomprimozic/type-
systems/tree/master/exte...](https://github.com/tomprimozic/type-
systems/tree/master/extensible_rows)

[2] [https://elm-lang.org/docs/records](https://elm-lang.org/docs/records)

------
_hardwaregeek
It's quite interesting seeing the polarization of people's opinions on type
systems. I have friends who adamantly argue that static types slow them down
and prevent them from adapting quickly. On the flip side I have friends who
claim they cannot write code without static types and that JavaScript is
unusable.

When really, both styles have their value. Static types are very useful and do
provide some nice safety. However, static types can also be quite annoying and
force you to acquiesce to the type system. You have to learn to "speak" types,
which can be very tricky. There's a joy that comes with the freedom of no
static types.

~~~
DonaldPShimoda
> You have to learn to "speak" types, which can be very tricky. There's a joy
> that comes with the freedom of no static types.

If you struggle with "speaking types" then that's an indication that you don't
understand the domain of values your code works with. Having a solid plan for
exactly what shape your data can take is empowering, not restrictive. To some
extent, certain restrictions provide greater freedoms in the sense that you
can make more accurate assumptions about your data without needing to perform
a bunch of run-time checks or write hundreds of unit tests to ensure your code
works in all possible situations.

~~~
danmaz74
Most often in imdustry, you _have_ to begin any complex project without a deep
understanding of your domain values, because that understanding only comes
iteratively. In my experience, the biggest drawback of static typing is that
it forces you to design your types when you still don't have enough knowledge,
and changing it later is painful.

~~~
DonaldPShimoda
> In my experience, the biggest drawback of static typing is that it forces
> you to design your types when you still don't have enough knowledge, and
> changing it later is painful.

I 100% understand where you're coming from, but I view this as an _advantage_
of static type systems. The "pain" is you having to go through your code and
make sure that you change your usages of given types to match the new
definitions, right? This is forcing you to make sure your code is still
correct with respect to the types.

Most dynamically-typed languages let you skip this part, but this causes you
to lose clarity. Is your old code still correct? Or is it just _mostly_
correct? Are there new unexpected edge cases being introduced that you didn't
bother to go check on due to faulty now-outdated assumptions?

Changing your types is a pain, I agree. But programs are just about data
transformations, and types are representations of the shape of those data.
Changing your types without changing every part of your program that touches
those types inherently means that your program is now running on invalid
assumptions. Sometimes it works fine, but I think it is better to _know_ it's
fine than to just say "Well, it seems to work in most cases."

~~~
danmaz74
The risk you describe is real, but good testing coverage lowers it in a way
that I find way more efficient than static type checking. When I have that
good coverage - which for me means the full pyramid of testing, not just unit
tests - I almost never find that problem.

Refactoring when you change your understanding of the domain can be a lot of
work also when you have dynamic typing and lots of tests, but if the tests are
well designed, I rarely feel that as painful as it was when I felt I was
battling with the type system.

PS: my main experience with static typing was with c++, it's possible that
more modern type systems could have improved the situation.

------
ergothus
As someone working in dynamic languages for years, I've noticed a certain
trend in arguing about type systems.

People will ARGUE about the benefits of compile-time checking.

However, when people point to concrete benefits, they tend to point to TOOLING
communication. (e.g. IDEs supplying functions/variables that provide the
necessary type) during writing.

Rarely do they notice these two are not the same thing.

I'm not in a place to say which is objectively correct (I'm both biased and
without the right kind of data), but I've seen this distinction come up time
and time again. I think it's the most interesting part (also the only part
that is interesting) about typing disputes.

~~~
naasking
> Rarely do they notice these two are not the same thing.

Types enable better tooling. That's why people mention tooling when types come
up.

~~~
ergothus
My point being that when I ask why static is better (from people arguing
that), they'll talk about compile-time checks, they won't talk about the
tooling.

When I ask them to show me examples, they don't point to bugs that were
prevented by compile-time checks, they'll point to how they find it easier to
write code because the IDE suggests things.

~~~
revvx
I think it's hard to point to "bugs prevented by compile-time checks" because
of the nature of type-checking.

I could just say that every time the compiler refuses to compile my code due
to a type error a bug is averted. Typos, wrong method signatures, interfaces
not implemented correctly. All those things had the potential to be bugs.
Small, detectable and perhaps even non-breaking, but it's still good to catch
them early on.

And of course, most of the time a good enough test suite would catch them. But
having the compiler warning you is so much faster! And much more reliable too,
because I'm not perfect and sometimes I make mistakes in my tests.

------
neilwilson
There is an argument that a static type system is a set of built in unit
tests, that you can activate by adding a command word in a particular
position.

And that the test harness on code in a dynamic languages is a domain specific
compiler.

The problems come when you want to turn off specific type tests for a
particular bit of code because it is deliberately bending the rules (I see
this a lot in Go), or the domain specific compiler you've built isn't
comprehensive in its coverage not just of lines of code, but the structural
complexity of the code.

------
codr7
I recently implemented a simple object system [0] using only closures and
macros in a Lisp-dialect [1] I'm working on.

One thing that struck me is how little it bothers me that objects look like
functions from the outside, and that any callable value could be used in their
place.

It helps that the whole thing is quarantined in a separate library and that
objects live in a separate namespace. And that it rests on top of a strongly
typed environment.

Judging a type system outside of its context based on some arbitrary check
list is pretty useless from my experience.

[0]
[https://github.com/codr7/g-fu/blob/master/v1/doc/functional_...](https://github.com/codr7/g-fu/blob/master/v1/doc/functional_objects.md)

[1]
[https://github.com/codr7/g-fu/tree/master/v1](https://github.com/codr7/g-fu/tree/master/v1)

------
jolmg
> I give the following general definitions for strong and weak typing, at
> least when used as absolutes:

> Strong typing: A type system that I like and feel comfortable with

> Weak typing: A type system that worries me, or makes me feel uncomfortable

What is he talking about? Is he trying to make a point? I always understood
the difference to be mostly about implicit type conversions. I think weak-
typing is also likeable.

~~~
dllthomas
I think he's trying to say that "strong-typing", in practice, _as used by some
speakers_ implies a static type system. And when used by other speakers does
not imply a static type system.

------
jononor
Note that the original is back up again now (at new location). Linked in first
paragraph.

~~~
sctb
Thanks! We've updated the link from
[http://blog.steveklabnik.com/posts/2010-07-17-what-to-
know-b...](http://blog.steveklabnik.com/posts/2010-07-17-what-to-know-before-
debating-type-systems).

------
bartimus
For some languages it's more about providing features for the IDE and less
about providing features for the language itself. It's probably the main
reason Microsoft started working on TypeScript for example.

