
Types Are Anti-Modular - swannodette
http://gbracha.blogspot.com/2011/06/types-are-anti-modular.html
======
dons
From the Haskell reddit discussion:

> It's a simple truth, indeed a tautology, that if you want the compiler to
> check the consistency of interactions between modules, then the compiler
> must know the information that it is checking. That's all that's being said
> here. It really shouldn't surprise anyone. If you dispose of typing, then
> someone still needs to know that relevant information in order to write
> correct code; but it is the programmer, rather than the compiler.

or, with more wit,

> Types are anti-modularity in the same sense stop signs are anti-
> transportation.

[http://www.reddit.com/r/haskell/comments/hs39c/gilad_bracha_...](http://www.reddit.com/r/haskell/comments/hs39c/gilad_bracha_types_are_antimodular/)

~~~
swannodette
The blog post isn't about correctness - it's about modularity. Lispers and
Smalltalkers are used to programming with very small compilation units -
individual methods and individual functions. Being able to fix running
programs w/o going through the whole compile cycle is a demonstrable
productivity gain.

Types and modularity are at odds. The antagonism is readily apparent in
languages that support dynamic _and_ static typing - Qi. It's RCEPL (Read-
Check-Eval-Print-Loop) has serious restrictions in comparison to its standard
REPL.

~~~
dons
> Being able to fix running programs w/o going through the whole compile cycle
> is a demonstrable productivity gain.

Indeed! Removing many sources of runtime failures at compile time is also a
demonstrable productivity gain. So good thing that static typing is no barrier
to dynamic extension or modification -- however, you must defer type checking
of splice points between components until later stages. (This was my PhD
thesis).

~~~
swannodette
The act of verifying types defeats modularity.

I think static typing is grand, but Gilad has a valid point.

~~~
jrockway
This is a serious "citation needed" assertion. What language doesn't verify
types? Python does. Lisp does. ("TypeError: cannot concatenate 'str' and 'int'
objects" or "wrong-type-argument: stringp 42")

I think the problem that people run into with types is when storing values for
future use. Imagine that you have a data structure that looks like:

    
    
       data Foo = Foo String
    

And then later on in your application, you have a function like:

    
    
       frobnicate :: IsString a => a -> a
    

Then, you write a program that looks like:

    
    
       program :: String -> String
       program = frobnicate . preprocess . Foo
    

This is all well and good as long as you only want your program to operate on
Strings. But if you want to change the type of your string data (perhaps to a
more efficient "ByteString", or something like that), then you are kind of out
of luck. You pigeonholed yourself into using String when you defined Foo as
"Foo String", and now you have a lot of work to do to feed that ByteString to
the function that actually cares about the type, "frobnicate", which can
already handle ByteStrings.

The problem here is not type checking, but rather that you built your program
in terms of an abstraction that was not generic enough, namely the "Foo" type.
If you were just using Python, then your program would have been:

    
    
       class Foo:
          def __init__(self, string):
              self.string = string;
    
       def frobnicate(whatever):
           return whatever
    

In this case, there would be no trouble using whatever type you wanted as the
"string", because you decide what type is OK at "frobnicate" time, instead of
at compile time.

The downside is that with your too-specific Haskell program, you know it's too
specific before you run it (because the compiler whines at you), but with the
Python program, you may only find out when your pager starts making a loud
noise at 3 AM, because Python has no idea what you're trying to do.

(Personally, having worked on a huge Python app that does no type checking
anywhere, I have to say I hate that technique. I prefer what I do in Perl,
which is to type check on object creation, but use type classes instead of
concrete types. You can still "late bind", but you also get errors when you're
making a mistake rather than deferring a decision. You can do the same thing
in Haskell, too, but with even better guarantees that you won't be waking up
in the middle of the night to unfuck your code.)

~~~
swannodette
I already said I'm not talking about correctness only modularity. Early/eager
verification breaks modularity.

~~~
jrockway
I would say, "bad program design breaks modularity", not early typechecking.
If your types are defined so that they are as broad as possible (i.e., "what
you really want"), then you will never even notice there is a type system,
until you try something impossible.

------
colanderman
Um... NO. This argument applies equally to functions and constants and is
equally wrong.

 _Types_ are _not_ anti-modular: the culprit is module systems that don't
allow type parameterization. Systems such as Java solve this problem for
functions and constants by allowing code to be parameterized over interfaces.
Bam, you can compile code using said functions and constants separately from
their implementation.

The solution for types is exactly the same: move types into interfaces. Guess
what, Objective Caml and Coq do this via module functors and it's glorious. If
my airline scheduler module needs to compile separately from my graph module,
well I can write:

module Scheduler(Graph: sig type graph val traverse: graph -> (string -> unit)
-> unit end) = struct ...blabla... end

and I can compile that code without ever writing a snippet of the graph module
-- not even defining the ADT!

TLDR: this problem has been solved 30 years ago by people who actually use
typed languages.

~~~
vog
Thanks for pointing this out. I was about to believe the post was correct, but
it seems it isn't. However, just to be sure I understand you correctly:

Is the module functor compiled only once, or once for every parameter type?
I'm asking because if it's compiled only once, the generated code might be
significantly less optimized. This might not play a role for complex parameter
types, but could miss lots of good optimization opportunities for simple types
such as int or char. How is this solved in those languages?

Also, is it just OCaml and Coq, or is this possible with other strongly typed
languages (such as Haskell) as well?

~~~
Chirono
Haskell's module system isn't very advanced. In fact, it about as simple as it
could get. This is pretty well known known and there are proposals floating
around to add more advanced features. But there generally isn't much need and,
as you mention, adding things like module type parameters prevents many cross-
module optimizations (which GHC does quite aggressively).

In practice, it doesn't seem to cause much of an issue, but this may just be
down to how Haskell tends to be used.

~~~
colanderman
Indeed, typeclasses in languages such as Haskell and Mercury can be (ab)used
for many of the same uses as functors. I've used both and there are merits to
each, but I still can't put my finger on when one is better than the other.

------
alok-g
Copy-pasting a comment on the original article here, since I have the same
comment/question:

Brian said...

Dynamic typing doesn't restore the modularity- it simply delays checking for
violations. Say module X depends upon module Y having function foo- this is a
statement which is independent of static vr.s dynamic checking. This means
that that modularity is lost- you can't have a module Y which doesn't have a
function foo. If module Y doesn't have a function foo, the program is going to
fail, the only questions are when and how.

What I don't get is why detecting the error early is a disadvantage. It's well
known that the earlier a bug is detected, the cheaper it is to fix. And yet,
all the arguments in favor of dynamic typing are about delaying the detection
of bugs- implicitly increasing their cost.

6/05/2011 6:49 AM

~~~
6ren
The article's just claiming that the modules of dynamic languages can be
compiled independently; not that it will be correct.

Just my thoughts: There's a bigger question here about types, though. I think
the argument is that the ceremony of types gets in the way more than it helps,
and it requires up-front design. Many people like dynamic languages for their
flexibility; and when you get bugs, you have to fix them anyway. There's an
assumption in typed languages that we can design our types well enough upfront
to help solve the problem.

It's interesting how types create a dependency on the interface (i.e. if you
use a type, you depend on it). One of the ideas of Abstract Data Types was to
reduce dependency on the internal implementation details; but you're still
dependent on the external interface. If you change the interface, it creates
problems (e.g. unit testing claims to give you confidence to refactor, but if
you change interfaces, you also have to change the tests...). _EDIT_ added
"claims to"

This seems intrinsic to modularity, and I can't really see any solution to
this; except for ways to make it easier to cope with interface change. Some
insights may come from webapp API's, where the dependency is more explicit
(you have to send and receive serialized messages).

~~~
alok-g
Maybe you got me closer to the answer, and you certainly seem to have more
experience on this subject than I do. I am still missing some part of the
picture, so let me ask:

Types: You think about the problem up-front to the best you can. Most likely
you later change the interface. The compiler now breaks the code and forces
you to fix it.

Dynamic: You are flexible, which means you can start your work without having
to think up-front (Q1: How is this good?). Later when you change the interface
(which may exist only in your mind, but it does as far as the modules are to
interact with each other in some way at all), the code still breaks but the
compiler won't tell you.

In either case, as you said "you have to fix them anyway". So Q2: How is
compiler not being able to complain about broken code (and possibly unit tests
finding it) helping?

One comment here cited an example where one can get away without introducing
errors while still changing the type ("f(g(x)"). But that is readily achieved
with statically typed languages like C++ as well.

~~~
6ren
Q1, you're assuming you _can_ design upfront; that is, that you understand the
problem, you have enough information to solve the problem, the world won't
change and so on.

Q2, the ceremony of types itself has a cost. You have to think about it, type
it - and you may need to change it. When types ripple through layers of calls,
the changes do too.

Note: I don't know the answer. I notice that dynamic languages _seem_ to be
becoming more popular (are they? or are they just more publicized?) In a
rapidly changing world, it's more important to adapt now than perfect. There's
a general trend, that because computers get faster but humans don't, more and
more of the work will be transfered to the computer. Dynamic languages do this
in the sense that they are less efficient than static languages - on the
_assumption_ that speed is the main benefit of static languages. Certainly,
the coder has less work to do. Another benefit is that it makes coding
accessible to less skilled people (and more skilled people who have less time
to devote to a particular task).

There are trade-offs. The first thing is to note what the trade-offs are. The
second thing is to note what groups of people use programming languages and
for what tasks. The third thing is to ask how those people value those trade-
offs, for those specific tasks. e.g. Perhaps sometimes, a crappy, hard-to-
maintain, only partially correct solution _now_ is better than a high-quality,
clear, correct solution _too late_?

------
ankrgyl
This article confuses the reader with a weird implicit definition of
modularity. The miscommunication is rooted here:

> Separate compilation allows you to compile parts of a program separately
> from other parts. I would say it was a necessary, but not sufficient,
> requirement for modularity.

The author goes on to assert that a _requirement_ for modularity is that
modules that depend on each other should be able to be compiled independently,
without even a specification to glue them together. This just doesn't make
sense. This might be a debatable point, but it has nothing to do with
modularity.

------
hxa7241
Lack of types gives the _illusion_ of more modularity.

As was mentioned -- there are still dependencies. The only difference is that,
without types, checking whether the parts fit together is deferred until later
-- but it will still be done. (All parts of software have to be connected, and
all have to be consistent.)

And therefore you could argue that types are _pro_ modular: because they allow
you to write separate pieces without needing the whole to test if it is OK --
which you cannot do without types.

------
baguasquirrel
Existential types? The author alludes to this, and then proceeds in ignorance
of what he'd just said for the rest of the post.

In Java, all you have for these is interfaces, so it's no wonder people think
that types are problematic.

Types aren't the problem. Crappy types are the problem. Not making the types
lightweight enough that people can dish them out at their pleasure, that's the
problem. When the math don't work well enough, make better maths.

~~~
swannodette
Except you can't unpack existential types, right?
[http://stackoverflow.com/questions/2300275/how-to-unpack-
a-h...](http://stackoverflow.com/questions/2300275/how-to-unpack-a-haskell-
existential-type)

~~~
dons
Unpack in this sense means "unbox", and you can specialize before you hide the
type.

------
dkarl
I disagreed strongly with this post and was about to post a rebuttal, but then
I realized there actually is a situation where untyped languages enjoy a
concrete, practical advantage because of the lack of compilation dependencies.
That situation is when you are writing a module (something that would be a
compilation unit in a typed language) that calls missing or nonexistent
library interfaces, and you want to test parts of the module that do _not_
depend on the missing APIs. In a typed language, you can't do it. You would
have to satisfy the dependencies first so your code would compile, or you
would have to copy snippets of your code out of the compilation unit into a
REPL or another file so they could be compiled separately. In an untyped
language, you can experiment with parts of the module and even start writing
tests without having to make sure all of the module's dependencies are
satisfied.

Aside from that one advantage, though, it's the same with types or without
them. For a module as a whole, the compilation dependencies that exist in a
typed language are the same as the runtime dependencies that exist in an
untyped language. Whether you're using a typed or untyped language, you can
write whatever code you want, but you can't do anything with it until you
satisfy its dependencies. With that one exception, of course: in an untyped
language, you can run code that _doesn't_ depend on the missing APIs but
happens to be in the same module as code that _does_ depend on them.

Overall, I don't think that amounts to much, especially compared to a typed
language like Scala where you can copy snippets into a REPL. Or even better,
Ensime is an Emacs mode that is supposed to let you work with Scala the way
Slime lets you work with Common Lisp. (I haven't tried it myself yet, but it
exists, and people are using it for real work.)

~~~
msie
Yeah I really hated this post. Starts off with a flame-baiting title. Then
resorts to very qualified statements to support it. After reading it I'm
wondering if it matters at all and I'm feeling dumb all throughout the
discussion. :-(

Update: Aarghh, it's just aggravating to read an academic stirring up the pot
over some point of theoretical purity. And I love theory too. ;-)

~~~
dkarl
It's the worst kind of theory: a theoretical point presented as a practical
insight without any explanation of what the practical ramifications actually
are.

------
prodigal_erik
If g returns an x and f takes an x, I can do f(g()) without knowing anything
else about type x. Even type inference will choose an x that makes g
compatible with f, or complain if there isn't any. So the system is still
modular at the source level, which is important because it keeps the project
comprehensible. What you don't have is binary-level modularity, but that's
just an efficiency hack that dates back to when computers were almost too slow
to run compilers. Now it should only matter to people who obfuscate by
refusing to ship source (or source-equivalent intermediate files, which at
least one Ada and one C++ implementation used to handle separate compilation
of polymorphic code) and living with the lack of whole-program optimization.

------
riobard
Why not structural typing?

