"class []" proposal Re: [Haskell-cafe] One thought: Num to 0 as ? to list?

Bulat Ziganshin bulat.ziganshin at gmail.com
Tue Aug 22 13:07:45 EDT 2006


Hello Arie,

Tuesday, August 22, 2006, 7:24:34 PM, you wrote:
>> I disagree. As a new learner to Haskell, I already have a hard time
>> keeping Constructors, Types, and Classes straight. I know what they
>> all are and what they all do, but sometimes I really have to think
>> hard to remember which is which in a piece of code. What helps my
>> understanding is that each has a specific place in the type signature
>> (which I guess includes 'nowhere' regarding constructors). Being able
>> to put Classes where Types go would just serve to muddle that
>> understanding.


> (*) In this specific instance one might (ab?)use the additional notation
> to create a gentle introduction to type classes in a course/tutorial: one
> of the first lessons/chapters could state that the type of '(+)' is 'Num
->> Num -> Num', where 'Num' means "some numeric type" (stressing that it
> is *the same* type in all three places), only later confessing that this
> is actually shorthand for something more elaborate, and that the vague
> notion of "some numeric type" can be made explicit using type classes.

to be exact, it is intended usage - like in the OOP model, Num or []
can specify not only concrete type - it's something that can have
subtypes. so, meaning of

(+) :: Num -> Num -> Num
or
sequence :: [Monad a] -> Monad [a]
or
hTell :: SeekableStream -> IO Integral

is simple and straightforward. And it's the _advanced_ material that
identifiers used here may be not only defined by type declarations,
but also by class declarations, and moreover - some of already studied
type names denote classes actually.

Subtyping introduced in very natural (at least for OOP souls) way. We
may, for example, have functions:

doit :: MemBuf -> IO Int
hRequestBuf :: MemoryStream -> IO Int
hTell :: SeekableStream -> IO Integral

and call doit -> hRequestBuf -> hTell and then return result, and all
will work fine because MemBuf is subclass of MemoryStream that is
subclass of SeekableStream while Int is subclass of Integral. We can
describe whole type hierarchy as having types at leafs and type classes
at internal nodes


As an example that clears my idea the following is function signatures
from one my module:

copyStream :: (BlockStream h1, BlockStream h2, Integral size)
           => h1 -> h2 -> size -> IO ()
copyToMemoryStream :: (BlockStream file, MemoryStream mem, Integral size)
                   => file -> mem -> size -> IO ()
copyFromMemoryStream :: (MemoryStream mem, BlockStream file, Integral size)
                     => mem -> file -> size -> IO ()
saveToFile :: (MemoryStream h) =>  h -> FilePath -> IO ()
readFromFile :: FilePath -> IO MemBuf

As one can see, there is only one function that don't uses classes,
and another one that can't be written using this syntax, another 3 is
just created for using this proposal. I don't say that such ratio is
typical, but at least i have a large number of polymorphic functions
in my library and found the way to simplify most of their signatures:

copyStream :: BlockStream* -> BlockStream** -> Integral -> IO ()
copyToMemoryStream :: BlockStream -> MemoryStream -> Integral -> IO ()
copyFromMemoryStream :: MemoryStream -> BlockStream -> Integral -> IO ()
saveToFile :: MemoryStream -> FilePath -> IO ()
readFromFile :: FilePath -> IO MemBuf

i think that second block of signatures is an order of magnitude more
readable

-- 
Best regards,
 Bulat                            mailto:Bulat.Ziganshin at gmail.com



More information about the Haskell-Cafe mailing list