
Mon, 13 Aug 2001 18:46:49 -0700, Ashley Yakeley
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