Optional Static Typing - Haskell?

Alex Martelli aleaxit at yahoo.com
Mon Dec 27 04:11:15 EST 2004


Donn Cave <donn at drizzle.com> wrote:
   ...
> And you probably think Eiffel supports fully modular programming, as
> I thought Objective CAML did.  But Alex seems not to agree.

Rather, I would say it's Dr Van Roy and Dr Haridi who do not agree;
their definition of "truly open programming" being quite strict, based
on modules "not having to know anything about each other".  BTW, while I
have looked into Alice yet, some people posting on this thread
apparently have -- I know the Alice whitepaper claims Alice adds to ML
just what is needed to support "truly open programming" -- features that
OCAML doesn't have, so, if the Alice researchers are right, your
assessment of OCAML is wrong; OTOH, since Alice _is_ statically typed
like any other ML dialect, they'd appear to rebut Van Roy and Haridi's
contention, too.  VR & H do mention Alice at the end of their pages
about static vs dynamic typing but they don't appear to acknowledge the
claim.  Maybe it all boils down to both Oz and Alice being still open
research efforts, making it difficult to assess fully what results they
can eventually achieve.


> The way I understand it, his criteria go beyond language level semantics
> to implementation details, like whether a change to a module may require
> dependent modules to be recompiled when they don't need to be rewritten.

Ah yes, definitely: Lakos' "Large Scale C++ Software Design" was the
first place where I met a thorough exposition of the evil effects that
come from modules "just needing to be recompiled, not rewritten" as soon
as the number of modules becomes interestingly large, at least with the
kind of dependency graphs that naturally emerge when designers are
focused on all other important issues of software design rather than
dependency control.

Basically, what emerges from Lakos' analysis, and gets formalized in
Robert Martin's precious "dependency inversion principle", is the
equivalent of the interface/implementation separation that you always
get e.g. in Corba, by writing the interface in IDL and the
implementation in whatever language you wish: both implementation and
client modules depend on an abstract-interface module, so the dependency
arrows go the "right" way (concrete depends on abstract) and the cycle
is more tractable.  But if you proceed by extracting the abstract
interface from a concrete implementation, that's a dependency too
(albeit for a tool that's not the compiler), and it's the wrong way
around... abstract depends on concrete.  (Main hope being that the
abstract part changes VERY rarely -- when it DOES change, the rebuild
cost is still potentially out of control).

You may use a separate language to express the interface (IDL,
whatever), you may use a subset of the implementation language with
strict constraints, but one way or another, if you want to control your
dependency graph and avoid the ills Lakos points out so clearly,
dependency inversion is an indispensable (perhaps not sufficient) tool.

Mike points out this breaks "once and only once", but then that same
principle is broken in any language where you express the type of a
variable twice -- in declaring AND in using it -- as is typical of most
statically-typed languages (even where the language does not mandate the
redundancy, as in *ML or Haskell, typical style in such languages is to
have the redundancy anyway; and AFAIK Eiffel _does_ mandate the
redundancy, just like C++ or Java do).

Dynamic languages have dependencies too, maybe not "reified" but
conceptually, _operationally_ there.  For example, if you consider the
contract (as in DbC) to be part of a type, that's how Eiffel works: it
diagnoses the type violation (breach of contract) dynamically, at
runtime.  And that's how's Oz, Python, Ruby, Erlang, etc etc, work too:
no type (signature, contract, ...) violation along any dependency arrow
need pass silently, they're diagnosed dynamically just like DbC
violations in Eiffel or more generally violations of other constraints
which a given language chooses not to consider "type-related" (e.g., if
the positive-reals are a separate type from general reals, sqrt(x) with
x<0 is a type violation -- if there's only a general reals type, it's
not -- hopefully, it's diagnosed at runtime, though).

Robert Martin's "Dynamic Visitor" design pattern is, I believe, an
instructive case.  The classic "Visitor", per se, has intractable
dependency problems and cannot possibly respect fully the open/closed
principle; Dynamic Visitor uses the escapes to dynamic typing allowed by
such tools as C++'s dynamic_cast (and Java's own casts, which work quite
similarly) to ensure a sane dependency structure.  If you don't have or
don't allow any escape from static typing, Visitor is somewhat of a
nightmare DP as soon as the kinds of visitors and visitees start
multiplying -- at the very least it's a build-time nightmare, even if
your language has tricks to save the need to change the code.  Martin
does a much more thorough job of exploring these issues and I'll point
to his essays rather than trying to expand this summary further.


> I don't know whether it's a reasonable standard, but at any rate hopefully
> he will explain it better than I did and you can decide for oneself whether
> it's an important one.

If you're building reasonably small systems, so that "recompiling the
world" is no big deal, dependency control may be considered a marginal
consideration -- some things such as dependency cycles you probably want
to eradicate anyway (so Visitor can still be thought of as nasty;-), but
there are no doubt many more important software development issues to
deal with than dependency control.  But build times suffer from typical
combinatorial explosions with the growth of number of modules and
complications in the dependency graphs, so, if you're building large
systems, this isn't the kind of efficiency problem that goes away in two
or three years thanks to Moore's Law.


Alex



More information about the Python-list mailing list