I Hate IO

Marcin 'Qrczak' Kowalczyk qrczak@knm.org.pl
14 Aug 2001 10:46:56 GMT


Mon, 13 Aug 2001 18:46:49 -0700, Ashley Yakeley <ashley@semantic.org> pisze:

> Do you have an example? Can't you simply substitute something like 
> "(Connection c) => c" for "Handle"?

Fortunately usually it's enough to read the contents and pass it as a
lazy String, instead of passing the Handle itself. This is what I've
done in a program which reads data from either a socket or file. In
this case it doesn't matter.

If the handle had to be passed explicitly (say, because it's for
output), having different types would work only sometimes. It would
require encapsulating the portion of the program working on a handle
into a polymorphic function, which is not always possible. For example
if a collection of handles of different types is formed, it just
wouldn't work. Putting objects of different types in a list requires
wrapping them in a common type. Having a common class is not enough.

In a language without subtyping types must more closely reflect the
interface rather than the implementation.

>>Items which are used interchangeably shouldn't have different types
>>in a language like Haskell which doesn't provide subtyping. 
> 
> Isn't that what classes are for?

Only sometimes.

> I've noticed that the more I use Haskell, the less I miss
> subtyping... classes and "data" unions are almost always sufficient.

They are sometimes a better match than subtyping (e.g. parametrizing
a gcd algorithm by a type) and sometimes they are not capable at all
(e.g. heterogeneous collections when the set of possible types is
open). I'm a big fan of Haskell's typing and I'm not a fan of OOP,
but Haskell is sometimes not as expressible as the OO way.

I have recently written an interpreter of a little language.
The runtime allows the following values: imperative functions,
terms, integers, and strings.

Many other objects can be provided as functions (e.g. variables,
dictionaries, file handles, dispatching on types of function arguments,
lazy evaluation), as long as their interface only exchange objects
of the above types.

But adding floating point numbers would require changing the runtime
(or stupid inefficiencies of exchanging numbers as strings), because
they need binary methods. Even if you checked that a function nearby
indeed represents a floating point number, you couldn't reach its
value as a Double!

About the only solution without changing the runtime for each new
type which needs binary methods is to add a value holding a Dynamic.

Classes won't help. Parametrization of objects of the interpreted
language by a type of foreign (Haskell) objects it may contain doesn't
make sense. It may contain objects of many types, and these types
are not known statically.

It wouldn't be a problem for a traditional OO language with checked
downcasting. They are like Dynamic, only better integrated with the
language, and with some interface hierarchy (you don't need to know
the precise type, some its interface is enough).

Function closures may overcome the problem of wrapping diverse types
in a common intreface, but it's not always possible. It doesn't allow
downcasting, you can only use the statically known interface.

A similar problem is in raising exceptions in Haskell. Ghc has an
exception holding a Dynamic precisely because the standard type system
can't express an open set of exception types without parametrizing
each action by exceptions it may throw.

> I don't know much about Haskell's multithreaded support, but if
> one wanted multiple threads using the same file, you'd need some
> kind of thread-locking (as suggested above). By contrast, with a
> "random-access" API it might even be possible to allow multiple
> simultaneous read operations.

You can have multiple Handles referring to the same physical file.

-- 
 __("<  Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/
 \__/
  ^^                      SYGNATURA ZASTĘPCZA
QRCZAK