Haskell classes and overloading (long) 
Author Message
 Haskell classes and overloading (long)

Hi:

I hope this is an appropriate post for this group ...

Here's the story: I'm really trying hard to learn Haskell at the
moment. I'm starting from a base of knowing Miranda pretty well
inside out. But there are some questions I just can't get my head
round, so I was wondering if someone could help me out:

-- Overloading and classes

Why doesn't Haskell allow simple overloaded functions? Why can't I,
for instance, define
        min :: Int -> Int -> Int
and     min :: String -> String -> String
without using classes? As far as I can see, the type system could
work out from the context which instance was actually being called.

Now ``classes'' aren't like real objects (in, say C++), right? Their
objects don't contain data, and they're just a way of specifying
overloading, right? .. or wrong?

-- Why no obvious types like C's `unsigned', ...

The program I'm writing slurps up a large (1M) data file and then
analyzes
it in various ways. The problem is that the data is things like physical
addresses, so what I need is a type like `unsigned long' in C. The
Int type doesn't fix, because it's signed, so comparisons between
addresses don't work right. I tried using HBC's Word type, but that
doesn't define `reads'*, making it less than useful. The Integer type
doesn't work either, for reasons that I can't quite discern from
the error messages, and anyway I'm worried that using Integer will
result in inefficient code.

I settled on using Word, as it seems like the best compromise, and it
supports bit shifts and so on (why aren't these built in???), so
I decided to write a `reads' instance myself, but to no avail. The
``obvious'' implementation was:

        instance Text Word =>
          readsPrec p x = readSigned readDec x

but this doesn't compile because Ints don't convert automatically to
Words. Yuk. Classes again ... What should I be writing here?

* (in GHC 0.26, which is what I'm using)

-- Debugging

OK. Now my programming isn't perfect, so I need to debug it. The problem
is that after I've built a monolithic executable in GHC there's just
no way to debug it. What I'd *like* to be able to do is either (1)
insert ``print'' statements - ie. display stuff while the program is
running, but outside the normal I/O - just dump an expression on
stderr whenever it's evaluated. Now, I realise that this is not
a pure thing to do, and expressions may be evaluated out-of-order, etc.,
but can't I just do it anyway? ... or (2) run the program under a
de{*filter*}, but none are available.

-- Monadic I/O

Most of the body of the program is pure functional. I don't pass the
IO monad around, mainly coz I don't understand monads too well ...
Ho hum. Now, if I want to call C (using GHC's _ccall_) this returns
a (the?) PrimIO monad. What do I do with it? Do I have to (somehow)
pass the IO monad into and out of every low level function in case
I need to call C at some low level?

-- Where to find out more?

I've read Hudak's ``A Gentle Introduction to Haskell''. Where do I go
now? As you can see, I still need to learn the basics, but I'd also
like to have a reference manual that describes 1.3 and the libraries
in some detail.

Thanks in advance .. Rich (willing but not able).



Sat, 02 Jan 1999 03:00:00 GMT  
 Haskell classes and overloading (long)

Quote:

> I hope this is an appropriate post for this group ...

Certainly

Quote:
> Here's the story: I'm really trying hard to learn Haskell at the
> moment. I'm starting from a base of knowing Miranda pretty well
> inside out. But there are some questions I just can't get my head
> round, so I was wondering if someone could help me out:
> -- Overloading and classes
> Why doesn't Haskell allow simple overloaded functions? Why can't I,
> for instance, define
>         min :: Int -> Int -> Int
> and     min :: String -> String -> String
> without using classes? As far as I can see, the type system could
> work out from the context which instance was actually being called.

This is what happens in ML.  The problem is that you can't work out
the overloading in all cases - hence the need for type hints.  Also,
if you allow general user-supplied overloading you get lots of {*filter*}
ambiguity problems.  The semantics of type hints are quite hazy and we
have decided to avoid them entirely.  By constructing an explicit
class, you allow the resolution of the overloading to be deferred
until execution time.  Languages based in Hindley - Milner don't have
the luxury of the bottom-up style of overloading (that is, you figure
out the types of the function arguments and then resolve the
overloading of the function) at compile time.

Quote:
> Now ``classes'' aren't like real objects (in, say C++), right? Their
> objects don't contain data, and they're just a way of specifying
> overloading, right? .. or wrong?

Well, they are real in the sense that, at execution time, you
sometimes need to pass something around (usually a dictionary) to
indicate which class instance is to be used.  But, no, there is no
`stateful' object associated with a class.  (Of course, in the
compiler you need to create lots of different structures to define a
class.  But the Haskell type system is fully defined at compile time -
there is no way to, for example, dynamicly add a new instance to a
class or change the method being dispatched by a type.)  So, you're
right in that classes are just a way of organizing perfectly ordinary
functions.  For example, in
 f :: Num a => a -> a -> a
 f x = x + x
you don't know exactly which version of + will be called by f but a
call to f will call whatever instance function is associated with the
object passed to f.  There is no magic in the value of x passed into f
- a nice property of the Haskell class system is that there is no need
to tag the value x.  (Dictionaries serve as a sort of disembodied tag
- they are better than tagging data objects since you can have a tag
for an object which does not exist yet, as used in read or
fromInteger, and a single tag may apply to many objects, as when a
list is passed into a function).  

Quote:
>  -- Why no obvious types like C's `unsigned', ...
>  <<Snip: perfectly reasonable need for unsigned integers>>

This is a really good example of something we should be formalizing in
a Haskell library.  There is no good reason this hasn't been done yet
- we just need to get moving on standard libraries.

Quote:
> -- Debugging
> OK. Now my programming isn't perfect, so I need to debug it. The problem
> is that after I've built a monolithic executable in GHC there's just
> no way to debug it. What I'd *like* to be able to do is either (1)
> insert ``print'' statements - ie. display stuff while the program is
> running, but outside the normal I/O - just dump an expression on
> stderr whenever it's evaluated. Now, I realise that this is not
> a pure thing to do, and expressions may be evaluated out-of-order, etc.,
> but can't I just do it anyway? ... or (2) run the program under a
> de{*filter*}, but none are available.

Every system has various `hacks' such as unsafePerformIO that let you
do what you want.  Again, we should standardize a nice debugging
library and make things easier for users like yourself.  The biggest
problem is that there is no agreement yet on how to debug Haskell and
what sort of support for debugging should be added.  We hope to get a
much nicer de{*filter*} built into hugs someday but not yet.  However,
adding `unsafe' print statements is possible in all of the Haskell
systems.

Quote:
> -- Monadic I/O
> Most of the body of the program is pure functional. I don't pass the
> IO monad around, mainly coz I don't understand monads too well ...
> Ho hum. Now, if I want to call C (using GHC's _ccall_) this returns
> a (the?) PrimIO monad. What do I do with it? Do I have to (somehow)
> pass the IO monad into and out of every low level function in case
> I need to call C at some low level?

It's really rather unfortunate that we have gone on so with these
fancy names like `monad'.  I don't understand monads either but it's
still pretty easy to figure out the monadic IO system if you can get
past all the wierd terminology.  Haskell 1.3 makes monadic programming
a little easier since it has special monad syntax.  In a nutshell, the
basic theory is this:
  a) operations which deal with the outside world (actions) are `tagged'
with
     a special type, IO.  This tag allows the type checker to keep
     everything straight.
  b) actions can only be combined using >>= and >>.  These define
     sequential evaluation and are similar to `;' in an imperative
     language.  
  c) Actions use expressions, but expressions cannot invoke actions
     (the type checker keeps you honest!).  So, if something is an
     action, everything leading up to that action must also be an
     action.  In other words, once you step out of the monad you can't
     get back in (unless you `cheat' using unsafePerformIO or
     something similar).  That's why
       f :: Int -> Int -> Int
       f x y = putStr ("x = " ++ show x ++ " y = " ++ show y) >>=
               return (x+y)
     doesn't work when you want to debug f.  You can't invoke the
     `putStr' action while evalating outside the monad.  
     Adding an unsafePerformIO would allow this to compile but you
     couldn't predict the behavior at execution time.
So, the bottom line is that if you have a C function that's in the
monad (you could very well have a pure C function which can be called
outside the monad) you have to `stay in the monad' to call it, as you
said.  It's not a matter of `passing the monad around' - it's using

Quote:
>> and >>= to define the control flow instead of ordinary functioncalling.

Th PrimIO thingy is some ghc-ism that user's ought not have to deal
with.

Quote:
> -- Where to find out more?

Right here!

Quote:
> I've read Hudak's ``A Gentle Introduction to Haskell''. Where do I go
> now? As you can see, I still need to learn the basics, but I'd also
> like to have a reference manual that describes 1.3 and the libraries
> in some detail.

A much more elaborate Gentle Introduction is in the works.  It should
be ready later this summer.  It will add all the 1.3 stuff and also
the executable material in the old Yale online tutorial.  (Certainly
one of the highlights of the late, lamented Yale Haskell system!)  If
there are any good Haskell texts out there I haven't seen them.
Especially since nobody covers all the changes made in 1.3 anything in
book form is sure to be obsolete.  Paul Hudak keeps threatening to
write a book but it hasn't happened yet!

Quote:
> Thanks in advance .. Rich (willing but not able).

John Peterson
Yale Haskell Project



Sat, 02 Jan 1999 03:00:00 GMT  
 Haskell classes and overloading (long)



Quote:

>> Why doesn't Haskell allow simple overloaded functions? Why can't I,
>> for instance, define
>>         min :: Int -> Int -> Int
>> and     min :: String -> String -> String
>> without using classes? As far as I can see, the type system could
>> work out from the context which instance was actually being called.

>This is what happens in ML.  The problem is that you can't work out
>the overloading in all cases - hence the need for type hints.  Also,

    But this type of overloading, sometimes called ad-hoc polymorphism,
is not available to ML programmers either. It is true that operations
like '+' can be used for real numbers as well as integers. ML allows
this, because it is expected of a programming language. But this is built
in to the language; a programmer cannot overload his own function
definitions this way.

   In the above example, you would most likely have two incompatible
function bodies, that is, the min for Ints would do some thing to its
integer arguments which would not make sense for strings, and the version
for strings would do something to its input arguments which would not
make sense for integers.

    Hence, you must have two seperate function definitions, with the
result that the second definition replaces the first in the ML
compilation environment; they are not combined in any way.

Quote:
>> Most of the body of the program is pure functional. I don't pass the
>> IO monad around, mainly coz I don't understand monads too well ...

>It's really rather unfortunate that we have gone on so with these
>fancy names like `monad'.  I don't understand monads either but it's

        There are a _lot_ of people out here who don't really _understand_
monads, but we have learned to write programs in the monadic style
anyway (I am one example of this). It's kind of a magical process; after
a while, you know what you have to do to make your program work; you only
dread that someone may someday ask you to explain what monads are.

    I mean, I can understand that we are passing values around in a
certain fashion, but the (category) theory behind it all is quite
opaque to me. I understand how to work in a monadic style of programming
by necessity, but I still feel as though my intelectual grasp of it is
very weak.

   And, for some real fun, try monadic programming in SML of New Jersey!
This is a fine implementation of ML; there is the ML typechecking
implemented perfectly (it will tell you whether your program is type
correct or not), and when your program has type errors, the compiler
attempts to give you a meaningful error message. But, when you are programming
in the monadic style, it turns out that the error messages are a real
puzzle sometimes, especially when your grasp of the monadic programming
style is weak.

       - Steve Pipkin



Sun, 03 Jan 1999 03:00:00 GMT  
 Haskell classes and overloading (long)

Quote:
>>> Why doesn't Haskell allow simple overloaded functions? Why can't I,
>>> for instance, define
>>>         min :: Int -> Int -> Int
>>> and     min :: String -> String -> String
>>> without using classes? As far as I can see, the type system could
>>> work out from the context which instance was actually being called.

>>This is what happens in ML.  The problem is that you can't work out
>>the overloading in all cases - hence the need for type hints.  Also,
>   But this type of overloading, sometimes called ad-hoc polymorphism,
>is not available to ML programmers either. It is true that operations
>like '+' can be used for real numbers as well as integers. ML allows
>this, because it is expected of a programming language. But this is built
>in to the language; a programmer cannot overload his own function
>definitions this way.

Quite correct.  From an implementation point of view, ML could handle
user-defined overloading in the same manner as built-in functions such
as +, but the whole thing is so dubious that ML prevents users from
extending the ad-hoc overloading.  Sorry if I misrepresented ML!
I used the ML example because it is the interaction of Hindley-Milner
and this style of overloading that causes problems.

  John



Sun, 03 Jan 1999 03:00:00 GMT  
 Haskell classes and overloading (long)


..........

Quote:
>   And, for some real fun, try monadic programming in SML of New Jersey!
>This is a fine implementation of ML; there is the ML typechecking
>implemented perfectly (it will tell you whether your program is type
>correct or not), and when your program has type errors, the compiler
>attempts to give you a meaningful error message. But, when you are programming
>in the monadic style, it turns out that the error messages are a real
>puzzle sometimes, especially when your grasp of the monadic programming
>style is weak.
>       - Steve Pipkin

I am a bit puzzled about using monads in SML.  According to my
understanding of monads what I would be doing is building up a huge
function composed from actions and other functions, using bind, until I
eventually get a suspension/closure that represents the entire program.
This is fed an initial world state and everything then happens.

Whereas in a lazy language the binding of the actions can be interleaved
with the execution of the actions making things go a lot smoother.

So I haven't been able to think of much use for monads in SML.

--
Anthony Shipman                 "You've got to be taught before it's too late,
TUSC Computer Systems Pty Ltd    Before you are six or seven or eight,
                                 To hate all the people your relatives hate,



Mon, 04 Jan 1999 03:00:00 GMT  
 Haskell classes and overloading (long)

Quote:
>I am a bit puzzled about using monads in SML.  According to my
>understanding of monads what I would be doing is building up a huge
>function composed from actions and other functions, using bind, until I
>eventually get a suspension/closure that represents the entire program.
>This is fed an initial world state and everything then happens.

>Whereas in a lazy language the binding of the actions can be interleaved
>with the execution of the actions making things go a lot smoother.

This is not the way monads work in SML.  Remember that one of the
arguments to bind is a function.  The body of that function is not
executed until the function is applied.  So things get interleaved
in SML just as you would expect.

Quote:
>So I haven't been able to think of much use for monads in SML.

It's true that monadic programming has not caught in SML the way it
has in Haskell.  Two of the major uses for monads in Haskell are
to simulate updateable state and exceptions.  Since SML already supports
these as language primitives, there is no need to simulate them with
monads.  However, some of the other applications of monads, such as
simulating non-determinism, can be useful even in SML.

Chris
--
-------------------------------------------------------------------------------
| Chris Okasaki       |                          Life is NOT a zero-sum game! |

-------------------------------------------------------------------------------



Mon, 04 Jan 1999 03:00:00 GMT  
 Haskell classes and overloading (long)


...........................

Quote:
>This is not the way monads work in SML.  Remember that one of the
>arguments to bind is a function.  The body of that function is not
>executed until the function is applied.  So things get interleaved
>in SML just as you would expect.
>Chris

I am still puzzled where the interleaving comes in.  We may have a
difference in terminology.  Here is an example that works with SML.

structure Monad =
struct

    type State = int

    type 'a M = State -> ('a * State)

    (* then my monad operators would be (with // for bindM) *)

    fun unitM a s = (a, s)

    fun op // (left:'a M, right: 'a -> 'b M) s =
    let
        val (a, s1) = left s
    in
        right a s1
    end

    infix //

    (*  and a run function *)

    fun runM (action: 'a M) ini_state =
    let
        val (result, final_state) = action ini_state
    in
        result
    end

    (* then I could write *)

    fun tick (s: State) = ((), s+1)

    fun double (s: State) = ((), s+s)

    fun times n (s: State) = ((), s*n)

    fun fetch (s: State) = (s, s)

    val action =           (
            tick        // (fn () =>
            double      // (fn () =>
            times 3     // (fn () =>
            fetch          ))))

    val result = runM action 0

    val _ = print(Int.toString result)
    val _ = print "\n"

end

Isn't the data value bound to 'action' some complex structure of closures
containing pointers to the other four actions?  

In general I am supposed to have the runM in the main() and its argument
will be a data value representing the execution of the program, none of
which is evaluated until the runM is called.  That might lead to a long
startup delay for the program with a lot of inefficiency.
--
Anthony Shipman                 "You've got to be taught before it's too late,
TUSC Computer Systems Pty Ltd    Before you are six or seven or eight,
                                 To hate all the people your relatives hate,



Tue, 05 Jan 1999 03:00:00 GMT  
 Haskell classes and overloading (long)

Quote:
>I am still puzzled where the interleaving comes in.  We may have a
>difference in terminology.  Here is an example that works with SML.
>[...]
>    val action =       (
>                tick        // (fn () =>
>        double      // (fn () =>
>        times 3     // (fn () =>
>        fetch          ))))
>[...]
>Isn't the data value bound to 'action' some complex structure of closures
>containing pointers to the other four actions?  

If this were evaluated lazily, then 'action' would be a thunk
containing pointers to the free variables in this expression, namely
'tick', 'double', 'times', and 'fetch'.

However, SML is strict, so // is applied to the first two of its three
arguments: 'tick' and (fn () => ...).  This returns a closure
containing pointers to 'tick' and to the closure for the anonymous
function, which contains pointers to its free variables 'double',
'times', and 'fetch'.  Note that the second and third applications of
//, and the application of 'times', have *not* been evaluated, and the
closures returned by the second and third partial applications of //,
and for the second and third anonymous functions, have *not* been
created, because they are delayed by the outer lambda (fn () => ...).
The execution of these delayed actions, and the creation of the
delayed closures, is "interleaved" with passing around the state,
just as it would be under lazy evaluation.

Essentially, SML has executed one more step than would have been
executed under lazy evaluation, but only one more step.  If you program
in the normal monadic style, this pretty much always holds true.

Now compare this to the following program:

  val action = let val c6 = fn () => fetch
                   val c5 = times 3 // c6
                   val c4 = fn () => c5
                   val c3 = double // c4
                   val c2 = fn () => c3
                   val c1 = tick // c2
               in c1 end

Here everything has been evaluated as far as possible without the initial
state, so there is no interleaving.  The value bound to 'action'
is indeed the "complex structure of closures" you referred to, with
one closure for each ci.  Perhaps this is what you had in mind?

Chris
--
-------------------------------------------------------------------------------
| Chris Okasaki       |                          Life is NOT a zero-sum game! |

-------------------------------------------------------------------------------



Tue, 05 Jan 1999 03:00:00 GMT  
 
 [ 9 post ] 

 Relevant Pages 

1. ML module system (vs Haskell type classes) -long-

2. Haskell typing problems (Unresolved Overloading)

3. Haskell overloading question

4. Re : overloading in Haskell/Gofer

5. Overloading in Haskell

6. Conflict Between Class-as-Module and Class-as-Type (long)

7. Reporter class - PERL-ish formatting class (LONG)

8. Summary: overloading, subtypes, and arrays missed in SML (long)

9. Overloading operators in Fortran 8x (a bit long)

10. Parcels - overloading classes

11. Overloading Interface Class Methods

12. a note on the Haskerl extension to Haskell [long]

 

 
Powered by phpBB® Forum Software