[Newbie] Quest for inheritance

Hi. This is my first message here so Hello to everyone. I'm just starting to learn Haskell and I really think it's a cool language. Coming from an OO world, I've been looking for ways to achieve inheritance as you get it in Java and the likes. I know OO and inheritance is not really the point of Haskell and that other mechanisms are provided to somewhat achieve reuse. But it's a way of programming I've been so used to that I feel lost without it. You might think I'm heading in the wrong direction. My mistake I have to agree. Let's take it as a learning exercise then. So I've been searching the net for *easy* ways to get it and the least I can say is that I'm confused. It soon became apparent that, inheritance not being a core feature of the language, many people have been hacking Haskell to come up with similar effects (btw I never thought there could be so many ways to reach the same goal...Haskell programmers are clever bastards). Of the many implementations that I've found, few are really simple and most achieve it with various levels of success and/or dodgy syntax. Here are the various techniques I've come across so far : (I'm far from understanding them all) 1. Through existential types As shown in the Shapes example at http://www.angelfire.com/tx4/cus/shapes/haskell.html. However, I couldn't spot the advantage over the standard version using normal classes at http://www.angelfire.com/tx4/cus/shapes/haskell98.html The getX function still needs to be defined in both RectangleInstance and CircleInstance. This is not what I call inheritance. Inheritance would make it possible to define getX only once in ShapeInstance. Or maybe the point was only to demonstrate polymorphism. But then what is the advantage of using existential types ? It just looks like more work compared to the standard version that also makes use of polymorphism. Or am I missing something ? 2. Through typeful heterogeneous collections This technique is described at http://homepages.cwi.nl/~ralf/OOHaskell/ and is able to bring most OO principles into Haskell. This seems to be a very good and thorough attempt. Unfortunately it looks more like a huge hack than a solution. The problem I have with it is the awkward syntax it requires, which to my newbie eyes doesn't look like Haskell anymore. 3. Through phantom types I couldn't find any idiot-proof documentation on this topic but this technique seems to be used to model inheritance. The few papers I've come across (http://research.microsoft.com/Users/simonpj/Papers/oo-haskell/overloading_co... , http://www.informatik.uni-bonn.de/~ralf/publications/Phantom.pdf ,http://www.informatik.uni-bonn.de/~ralf/publications/With.pdf ) seem very interesting but all go way over my head. 4. Through O'Haskell (http://www.cs.chalmers.se/~nordland/ohaskell/) Which is a non standard extension of Haskell that seems to be dead anyway. 5. With this : http://www.cs.utexas.edu/ftp/pub/techreports/tr01-60/tr01-60.pdf This is a very interesting paper, at least for a newbie like me who's used to Java. The aim of this paper was to develop a same application in both Java and Haskell and to compare the resulting implementations afterwards. What interested me in this was the mechanism they used to model inheritance (described on page 16 & 19), based on data types instead of classes. The idea being that all constructors of the Employee datatype have a same parameter, 'inCommon', of type 'CommonOfEmployees'. The data type CommonOfEmployees consists of all the fields shared by all types of employees. By then defining polymorphic functions that only use the 'inCommon' property of any employee, they've managed to instantiate these methods only once. My explanation must be horrible to you but you'll certainly get it by reading their paper. The Haskell source code can be found here :http://www.cs.utexas.edu/ftp/pub/techreports/tr01-60/Haskell_Impl/ The conclusion I drew from my quick & early findings is that the latest method is the simplest way to get inheritance in my programs. I guess my question now is this : Are there other ways to achieve inheritance in Haskell ? Simpler techniques ? Or more accurate / comprehensive ? Would you have pointers to useful resources ? I apology in advance for the errors, false assertions, misconceptions, confusions made in this document. I'm still learning Haskell. I'd just like to get the most of it. Cedric Paternotte

Cédric Paternotte wrote:
Hi. This is my first message here so Hello to everyone.
I'm just starting to learn Haskell and I really think it's a cool language.
Me too :)
I know OO and inheritance is not really the point of Haskell and that other mechanisms are provided to somewhat achieve reuse. But it's a way of programming I've been so used to that I feel lost without it. You might think I'm heading in the wrong direction. My mistake I have to agree. Let's take it as a learning exercise then.
Me too :)
5. With this : http://www.cs.utexas.edu/ftp/pub/techreports/tr01-60/tr01-60.pdf
I've been thinking about slight generalization of this lately. Here are my semi-backed thoughts as of now. First of all, in Haskell there will be strict separation between interfaces and data, so almost every method will be declared twice. This is not so strange to anybody programing in Java, but for C++ programmers can be. Inheritance relation is specified after data. There is also separation between two concepts: what interfaces each piece of data implements and which intefaces given interface inherits. So: {-# OPTIONS -fglasgow-exts -fallow-undecidable-instances #-} module Main where -- general inheritance relation class Inherits b x where get_super :: x -> b -- declare interface with one method class IA a where get_a :: a -> Int -- define data with one field data DA = DA { da_field :: Int } -- say how data DA conforms to interface IA instance IA DA where get_a x = da_field x -- declare some other interface IB -- note: IB is unrelated to IA class IB a where get_b :: a -> String -- data that inherits fields of DA and adds one another field data DB = DB { db_super :: DA, db_field :: String } -- DB inherits fields and methods of DA instance Inherits DA DB where get_super x = db_super x -- data DB implements interface IB instance IB DB where get_b x = db_field x -- some other random data data DC = DC { dc_super :: DA } -- DC implements interface IB instance IB DC where get_b x = show (get_a x) -- and inherits DA instance Inherits DA DC where get_super x = dc_super x -- now the tricky part: state that every data x inheriting DA -- implements all interfaces of DA (repeat for each interface) instance (Inherits DA x) => IA x where get_a w = da_field (get_super w) main = do let db = DB (DA 123) "zzz" let dc = DC (DA 123) putStrLn $ show (get_a db) putStrLn $ show (get_a dc) putStrLn $ show (get_b db) putStrLn $ show (get_b dc) As you see there is much more writting as in Java. But this gives better control over inheritance and subsumption because everything must be stated explicitly. Multiple inheritance is allowed :) Also it is "private inheritance" (as in C++) by default. There are some problems left: how to update a field? or how to make inheritance transitive. I don't know it yet :)
I guess my question now is this : Are there other ways to achieve inheritance in Haskell ?
Me too:) My proposal (above) is about the level of 'OO' things done in procedural languages (example: C with GTK+ library). There must be a better way. Any comments? -- Gracjan

Hi Gracjan,
On 6/5/05, Gracjan Polak
First of all, in Haskell there will be strict separation between interfaces and data, so almost every method will be declared twice. This is not so strange to anybody programing in Java, but for C++ programmers can be. Inheritance relation is specified after data. There is also separation between two concepts: what interfaces each piece of data implements and which intefaces given interface inherits. So:
I don't mind declaring functions headers more than once as long as I don't have to do it with their body.
{-# OPTIONS -fglasgow-exts -fallow-undecidable-instances #-}
module Main where
-- general inheritance relation class Inherits b x where get_super :: x -> b
-- declare interface with one method class IA a where get_a :: a -> Int
-- define data with one field data DA = DA { da_field :: Int }
-- say how data DA conforms to interface IA instance IA DA where get_a x = da_field x
-- declare some other interface IB -- note: IB is unrelated to IA class IB a where get_b :: a -> String
-- data that inherits fields of DA and adds one another field data DB = DB { db_super :: DA, db_field :: String }
-- DB inherits fields and methods of DA instance Inherits DA DB where get_super x = db_super x
-- data DB implements interface IB instance IB DB where get_b x = db_field x
-- some other random data data DC = DC { dc_super :: DA }
-- DC implements interface IB instance IB DC where get_b x = show (get_a x)
-- and inherits DA instance Inherits DA DC where get_super x = dc_super x
-- now the tricky part: state that every data x inheriting DA -- implements all interfaces of DA (repeat for each interface) instance (Inherits DA x) => IA x where get_a w = da_field (get_super w)
This is smart. So I understand the point of this part is to forward the "function call" to the parent (through get_super). All you have to do is to define these forwards in each inheriting data. Does it also mean that, in each inheriting data, you have to define these forwards to all your parents (meaning not only to the one just above, but all of them) ? In other words if I was to define a data DD which inherits from DB (and thus also from DA), will I have to define forwards for both get_a and get_b ? If not, how would you declare it ?
As you see there is much more writting as in Java. But this gives better control over inheritance and subsumption because everything must be stated explicitly. Multiple inheritance is allowed :) Also it is "private inheritance" (as in C++) by default.
I think I like this way of dealing with inheritance. There's a bit more typing indeed and it's kind of limited but it has the advantage of being relativily simple to put in action. What I really like with this is that you can come up with new data types inheriting DA without having to change anything in the declaration of DA. I guess you'd just better avoid having too many levels of hierarchy as it tends to get more and more verbose ;) Cédric

Cédric Paternotte wrote:
Hi Gracjan,
This is smart. So I understand the point of this part is to forward the "function call" to the parent (through get_super). All you have to do is to define these forwards in each inheriting data.
Yes. I think this is the whole point of inheritance :)
Does it also mean that, in each inheriting data, you have to define these forwards to all your parents (meaning not only to the one just above, but all of them) ? In other words if I was to define a data DD which inherits from DB (and thus also from DA), will I have to define forwards for both get_a and get_b ? If not, how would you declare it ?
This is exactly what I described as "private inheritance". If you have (Inherits DA DB) and (Inherits DB DC) this does not mean that you have automatically (Inherits DA DC). Why? This would require instance: instance (Inherits a b,Inherits b c) => (Inherits a c) where ... but this summons known problem: multiple inheritance. How to chose b? Imagine such situation: data DA; data DB; data DC; data DD instance Inherits DA DB where ... instance Inherits DA DC where ... instance Inherits DB DD where ... instance Inherits DC DD where ... DD inherits DA *twice*. So b in above instance declaration would not be determined uniquely.
As you see there is much more writting as in Java. But this gives better control over inheritance and subsumption because everything must be stated explicitly. Multiple inheritance is allowed :) Also it is "private inheritance" (as in C++) by default.
I think I like this way of dealing with inheritance. There's a bit more typing indeed and it's kind of limited but it has the advantage of being relativily simple to put in action.
I agree with typing, but compared to Java this is actually not limited but more powerful, because it gives greater control over inheritance. Most important aspect to me is that inheritance can be specified *after* data declaration. Imagine you have some strange library that has DA and DB, that are obviosly in generalization-specialization hierarchy, but some jerk forgot to inherit one from another. In Java you are toast, in Haskell you can specify inheritance relation in your code :)
What I really like with this is that you can come up with new data types inheriting DA without having to change anything in the declaration of DA.
I guess you'd just better avoid having too many levels of hierarchy as it tends to get more and more verbose ;)
If you stick to single inheritance there is other way to simulate OO in Haskell. Look for "phantom types". Whole wxHaskell (for example) is based on this concept.
Cédric
-- Gracjan

On 6/6/05, Gracjan Polak
If you stick to single inheritance there is other way to simulate OO in Haskell. Look for "phantom types". Whole wxHaskell (for example) is based on this concept.
I heard about them indeed but barely found clear explanations of it. Any useful pointer you're aware of maybe ? Cédric

Hi Cédric, On 05/06/2005, at 6:52 AM, Cédric Paternotte wrote:
Here are the various techniques I've come across so far : (I'm far from understanding them all) [..]
Manuel Chakravarty and I also wrote a paper titled "Interfacing Haskell to Object-Oriented Languages" that you might find useful: http://xrl.us/ftux (Link to www.algorithm.com.au) -- % Andre Pang : trust.in.love.to.save http://www.algorithm.com.au/

Hi Andre,
Manuel Chakravarty and I also wrote a paper titled "Interfacing Haskell to Object-Oriented Languages" that you might find useful:
I've been reading it and from what I understood the technique you've come up with is used to model foreign OO language hierarchies so that Haskell can interface with them. My question is can you use it to code in Haskell in a OO way or is it just meant to provide bridges to these foreign OO objects ? I noticed most examples in the paper were related to the matters of interfacing. Or is it more than that ? Could you, for instance, craft a version of, say, the Shapes example with this approach ? Thanks, Cédric Paternotte

On 06/06/2005, at 3:47 PM, Cédric Paternotte wrote:
Manuel Chakravarty and I also wrote a paper titled "Interfacing Haskell to Object-Oriented Languages" that you might find useful:
I've been reading it and from what I understood the technique you've come up with is used to model foreign OO language hierarchies so that Haskell can interface with them. My question is can you use it to code in Haskell in a OO way or is it just meant to provide bridges to these foreign OO objects ?
I don't think there's any real barrier to coding Haskell in an OO way, though my personal motivation for the paper was really to use it as a primitive bridging layer, and build a more functional interface on top of it. wxHaskell is a good example of this: it provides a more Haskell-like interface on top of a basic layer to wxWidgets via a layer named wxCore. Note that Mocha (which is discussed in the paper) has been succeeded by HOC: http://hoc.sf.net/, although that's probably of serious interest to you if you have a Mac.
I noticed most examples in the paper were related to the matters of interfacing. Or is it more than that ? Could you, for instance, craft a version of, say, the Shapes example with this approach ?
You could definitely craft up a Shapes example with the interfaces presented in the paper; if you think of an OO library that exports Shapes as a public API, then making an interface to this library vs crafting the Shapes example is the same thing. -- % Andre Pang : trust.in.love.to.save http://www.algorithm.com.au/

Hello Cédric, Sunday, June 05, 2005, 5:52:13 PM, you wrote: CP> What interested me in this was the mechanism they used to model CP> inheritance (described on page 16 & 19), based on data types instead CP> of classes. The idea being that all constructors of the Employee CP> datatype have a same parameter, 'inCommon', of type CP> 'CommonOfEmployees'. CP> The conclusion I drew from my quick & early findings is that the CP> latest method is the simplest way to get inheritance in my programs. OO approach is only one technology of "divide and conquer", just very popular in last 20 years. moreover, in OO world all the other possible programming techniques are gone to be modelled via this uniform mechanism. as a result, programmers with strong OO-only backgound tend to search solution for any programming problem in terms of classes and inheritance between them FP provides your another basic programming block - function call, or parameterized computation. quick comparision with OO basic block - class will tell you that function call is a more simple, basic element while class interface consists of several such function calls. so, simplest analogy of class interface is just tuple of functions: createCircle x y r = let draw = ... move x y = ... changeColor c = ... in (draw, move, changeColor) createRectangle x y w h = let draw = ... move x y = ... changeColor c = ... in (draw, move, changeColor) such types of interfaces cover 90% of situations where you must use classes in C++ (well, only 30%. another 60% covered by even simpler construction: data Shape = Circle x y r | Rectangle x y w h draw (Circle x y r) = ... draw (Rectangle x y w h) = ... imho, FP programming require that you think in terms "what operations i need for this object" and "what data each this operation will need" instead of OO's "what is a class hierarchy". code reusing is reached by creating general functions which receive divergent subfunctions as their parameters: calcCRC (fOPEN,fREAD,fCLOSE) = do h <- fOPEN crc <- newIORef 0 buf <- mallocBytes 65536 let go = do len <- fREAD h buf 65536 crc <- updateCRC crc buf len when (len>0) go go fCLOSE h readIORef crc calcFileCRC filename = calcCRC (hOpen filename, hGetBuf, hClose) calcCompressedDataCRC file algorithm = do calcCRC (startDecompression file algorithm, decompressBlock, finishDecompression) You can find more examples of using such technique in my program (http://freearc.narod.ru), see for example allocator/memoryAllocator, ByteStream.createFile/createBuffered/create, read_file -- Best regards, Bulat mailto:bulatz@HotPOP.com
participants (4)
-
Andre Pang
-
Bulat Ziganshin
-
Cédric Paternotte
-
Gracjan Polak