Donn Cave <[EMAIL PROTECTED]> 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 -- http://mail.python.org/mailman/listinfo/python-list