[Haskell] Per-type function namespaces (was: Data.Set whishes)

oleg at pobox.com oleg at pobox.com
Thu Feb 26 18:13:08 EST 2004


Hello!

> So, how can you come up with a type class which provides a polymorphic
> 'add' function, considering you don't even know how many parameters
> each data type's individual add function uses?

Very easily: every Haskell function takes only one
argument. Always. Ever.

> For example, say I'm writing the Data.Complex module; there's a
> function in that module "phase :: RealFloat a => Complex a -> a".  So,
> how do you put this phase function into a type class?  Perhaps you
> could abstract away from the RealFloat and Complex bits, so you have a
> phase function which is generalised to work over a Num and an
> arbitrary data type instead; e.g. "class Phase c where phase :: Num a
> => c a -> a".  But what happens if, say, somebody adds a Moon data
> type, and they want to write a phase function which returns the phase
> of such a moon?  Phases of the moon certainly aren't Nums, nevermind
> the fact that you probably want to supply your moon phase's function
> with some sort of date as an extra parameter, which means the Phase
> type class isn't flexible enough.

Here's the code that does exactly as you wish:

> {-# OPTIONS -fglasgow-exts #-}
>
> import qualified Complex 
>
> class Phase a b | a -> b where
>   phase:: a -> b
> 
>
> instance (RealFloat a) => Phase (Complex.Complex a) a where
>     phase = Complex.phase
>   
> data MoonPhase = P1 | P2 | P3 | P4 deriving Show
>
> instance Phase Int MoonPhase where
>     phase x = if x `mod` 4 == 0 then P1 else P4
>   
> instance Phase MoonPhase (Int->Int) where
>     phase P1 x = x
>     phase P2 x = x+1
>
> main = do
>         putStrLn $ show $ phase ( (1.0::Float) Complex.:+ (1.0::Float))
> 	  putStrLn $ show $ phase (0::Int)
> 	  putStrLn $ show $ phase P1 (2::Int)

You can evaluate a phase of a complex number, get a phase of the moon
corresponding to some integer, and even convert a phase of the moon to
a time (given another integer as a reference time). Whereas the first
two functions take one argument, the latter phase takes "two
arguments". The class Phase takes the classical first-argument
overloading. Other overloading schemes are possible (e.g., the ones
that overload based on the result -- something that C++ just can't do:
e.g., Read). If we need to evaluate phases of Saturn moons (and we
overload on the first argument), we can resolve the overloading using
newtype:

> newtype SaturnTime a = ST a
> instance Phase (SaturnTime Int) (Int -> MoonPhase) where
>    phase x moon_index = P1

newtypes add no run-time overhead, and actually help in making the
code more explicit.

Regarding Koening lookup: as I read in DDJ, it's just a hack! First
the committee added the namespaces, and then realized that using
operators like << became hugely inconvenient. So Koening came up with
a hack. Shouldn't a language be designed in a more systematic way?

Speaking of the language design, November 2003 issue of Dr.Dobbs
J. has an interesting article: "C++ Compilers and ISO Conformance" [by
Brian A. Malloy, James F. Power and Tanton H. Gibbs, pp. 54-60].

Here's a summary. C++ standard has been ratified by the ISO Committee
in September 1998. There is no conformance suite however. So, we
cannot tell how well a particular compiler complies with a
standard. The authors of an article decided to create an approximate
conformance suite -- from the examples given in the standard
itself. It's a hard job -- the examples aren't meant to be a compiled
code, so some declarations and other pieces have to be filled in. The
result cannot be considered a truly compliance suite because not all
features of the language are illustrated in examples, and the
distribution of the examples is uneven. Nevertheless, it's a start.

The authors of the article have tested several compilers. The bottom
line -- after five years, no single compiler fully complies with the
standard. The best compiler, from the Edison Group (a three-person
company) fails only 2 tests. Intel's compiler fails three. Visual C++
7.1 from Microsoft fails 12. Gcc 3.3 fails 26. The latter number shows
that a wide community participation and OpenSource do not necessarily
lead to a better product. Gcc 3.3 is also one of the slowest
compilers.

But there is worse news for C++. C++ Language Standard consists of 776
pages, describing C++ language and the C++ core library. At present,
411 points in the C++ language part and 402 points in the library
part have been identified as questionable or outright erroneous. 93
language issues have been already acknowledged as errors. That is,
EVERY page of the standard, on average, contains some issue! The
committee obviously didn't bother to check their examples. Well, even
now there isn't a compiler that complies with the standard -- whatever
the compliance may mean.

Not only programmers don't know what some C++
rules mean. Not only compiler writers are puzzled. Even the committee
itself obviously doesn't know how _many_ features are supposed to
work. Can you imagine more shoddy work? 

Incidentally, here's one questionable example from the standard (which
has been acknowledged as an error in the Technical Corrigendum 1). 

typedef int f;
struct A {
   friend void f(A &);
   operator int ();
   void g(A a) { f(a); } 
};

f(a) is ambiguous: it could mean an invocation of method A.f. OTH, it
may mean casting 'a' to an integer: f(a) may read int(a) (if typedef
takes effect), which is a cast. Both interpretations are valid. The
authors of the standard assumed that a compiler could
disambiguate. After the compiler writers tried and failed, the
committee admitted that perhaps the example shouldn't compile after
all.


More information about the Haskell mailing list