[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: More on dispatching by function return type

On Dec 16, 2003, at 5:05 PM, Ken Shan wrote:

> John Clements claims (I think correctly) that "you can't write down a
> deterministic operational semantics for Haskell that doesn't involve
> the result of the type proof".  He also says that "for other languages,
> you can", excluding C because it is nondeterminism.  In fact, even a
> deterministic C++ doesn't have a deterministic operational semantics
> that doesn't involve the result of the type proof, because -- although
> C++ doesn't resolve overloading by return type -- you can use output
> reference arguments to rewrite your C++ code to always return void.
> Indeed, I can write
>     int x;
>     cin >> x;
>     return x/2;
> as well as
>     bool x;
>     cin >> x;
>     return !x;
> to take advantage of overloading on operator>>.  In C++, such use of
> overloading does not make my program more concise, because I need to
> write down the type of x explicitly.  A more trivial example of how
> types affect behavior in C++ is
>     struct A { A() { cout << "Constructing A" << endl; }
>     struct B { B() { cout << "Constructing B" << endl; }
>     int main() { A x; }
> Here the type of x affects which constructor gets called.  Of course,
> you could regard the type declaration for x to be part of the program
> proper rather than an annotation, but then again, one could regard type
> annotations in Haskell to be part of the program proper rather than an
> annotation, and devise an operational semantics whose very first stage
> performs type-checking and type-class overloading resolution.

This is exactly the distinction I'm getting at.  An operational 
semantics whose domain is "programs the programmer writes down" is 
going to get to see the types written down by the programmer, and _not_ 
those inferred by the type system. So the C++ situation is different 
from the Haskell situation.  A "very first stage" which performs 
type-checking is explicitly fenced out by the oracular type-checker I 
tried to describe before.

At the end of the day, this is an argument about a sociological 
problem: how confused can the programmer get?  My point is that I can 
draw a fairly well-defined "line in the sand" between languages which 
"have an operational semantics" and those which don't.  The problem 
here is one of locality: how far away in the program do I have to look 
in order to understand what's happening in my little corner of it?  In 
languages like ML, Scheme, etc. etc. etc., I can figure out what a 
function call will do, knowing only the inputs and the code of the call 
itself (plus all that icky mutable state).  In the scenario you've 
described, Haskell appears to make that impossible.

Let me take a step back, as well, and point out that the _whole_point_ 
of purely functional programming is to enable componential reasoning 
about program fragments; it's curious that Haskell, of all languages, 
would break this.

Keep in mind that I have no problem at all with inference as a part of 
type-checking; ML falls into the "has an operational semantics" 
category, as far as I can tell.  It's just that the result of 
type-checking is simply a "yea" or "nay" on whether the program is 


Here's the other side of the coin, as I see it: Macros.  Macros (e.g. 
in Scheme) _also_ have the "bad" property that unless I know what 
macros are present, I cannot hope to understand what a piece of code 
does.  My take on this (not an original thought, of course) is that the 
macros are part of the language, and not part of the program, so the 
operational semantics for a program written in Scheme must already take 
into account the macros that are present.

So how does this apply to Haskell?  It would appear that Haskell's type 
classes are a different form of program abstraction, and therefore that 
just as a Scheme program expands away the macros to reveal the "core" 
program, a Haskell program is elaborated through type class resolution 
and type checking into the "core" program.  The difference I'm 
suggesting is that in the Scheme case, I can push these expansion rules 
into the language's semantics, because they're not whole-program 
transformations.  In Haskell, on the other hand, type-class resolution 
and type-checking certainly appear (pardon my ignorance) to be global