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

Re: OT: Language War (Re: "C" Manual)



Lo, on Tuesday, January 1, dman did write:

> On Mon, Dec 31, 2001 at 09:27:36PM -0500, William T Wilson wrote:
>  
> | Casting you can't really get away from nor do you really need to.  In fact
> | the more strongly typed the language is, the more casting you have to do.
> 
> This statement is incorrect.

Agreed.

> The strength and staticness of typing are two independent properties.

Also agreed.

However, I think that the flexibility of a type system is more important
than its `strength' for removing the need for casts.

When do people use typecasts in C/C++?  In my experience, they tend to
happen in the following situations:

  1) conversions between data types (char and int, int and float, etc.)
 
  2) downcasts in a class hierarchy, as in Java.  (Note that `upcasts'
     aren't really casts: if B is a subclass of A, then an instance of B
     *is* an instance of A, no cast needed.)

  3) Accessing external data in a callback function.  Callbacks in C and
     C++ often take a void* parameter that points to data supplied by
     the programmer when he registers the callback; to access this data,
     it is necessary to cast it back to the original type.

How do more flexible type systems handle these?

  1) There are conversion functions.  Scheme does this with, e.g.,
     char->integer and integer->char, or exact->inexact and
     inexact->exact.  (The latter two convert between integers and
     floating point numbers.)  In many cases, this is what C's casts and
     C++'s static_cast really do; they just use a typecast notation.
     Many programmers tend to follow this trend when writing their own
     conversions, although I usually don't---implicit conversions worry
     me.

  2) As several people have pointed out, too many downcasts indicate a
     design flaw.  They are, however, useful in some situations, so (I
     believe) many advanced languages with a static type system support
     these.  However, as in Java, there's a run-time check that goes
     with it.  C++'s dynamic_cast gets this right.

  3) There are a variety of strategies here.  My personal favorite is
     to use first-class closures to implement callbacks; the external
     data is no longer supplied as a parameter but is simply available
     to the callback function due to normal scoping rules.  If you're
     using a language without closures, like C++ or Java, you can hack
     it up:

     class Closure
     {
     public:
         virtual void invoke(void) = 0;
     };

     Subclass this, implement invoke(), and supply a reference to the
     subclass instance as your callback function.  The data normally
     recognized by the void* is now private data members in the callback
     instance; no cast necessary.

     Alternatively, supply the callback system with enough smarts to
     handle types.  Instead of

        void register_callback(void (* cb)(void *), void *data);

    write something like

        template<typename T>
        void register_callback(void (* cb)(T), T data);

    although most advanced languages typically use a more concise
    mechanism for this sort of polymorphism.

With the exception of downcasts, casting is generally only necessary if
your type system isn't very expressive.

> I've heard that haskell has an even better type system, but I haven't
> had time to learn it yet.

Most of my experience with advanced programming languages has been in
Scheme, which (despite what many people believe) *is* strongly-typed and
type-safe, but it checks all types at runtime rather than compile time.
As a result, I'm not really up on languages with compile-time type
verifications systems, though I've been meaning to investigate them for
a while now.  At any rate: I've heard that ML's type system is easier to
learn than Haskell's; you may want to start there first.

Richard



Reply to: