6
\$\begingroup\$

A Java desktop application exposes the class/containment hierarchy of its Swing GUI through RPC. I'm building a Haskell client that connects to the application. The objective is to be able to perform queries like "find all the buttons of width less than w, located within screen rectangle r" over a data structure on the Haskell side.

I'm not sure about how to best represent the structure, especially regarding inheritance/subtyping. I have tried two approaches. The first one:

data Component = Component
    {
        componentId::Int,
        pos::(Int,Int),
        dim::(Int,Int),
        componentType::ComponentType,
        children::[Component]
    }

data ComponentType =
     ButtonComponent Button
     -- other component types here
    |OtherComponent

data Button = Button
    {
        buttonText::Text
        -- other button attributes here
    }

I'm also considering a second solution using existential types, together with Typeable to implement "downcasting":

data Component = Component
    {
        componentId::Int,
        pos::(Int,Int),
        dim::(Int,Int),        
        children::[AnyComponent]
    } deriving Typeable

data AnyComponent = forall a . ( Typeable a, HasComponent a ) => AnyComponent a 

class HasComponent a where
    base :: a -> Component
    downcast :: Typeable b => a -> Maybe b

instance HasComponent Component where
    base = id       
    downcast = cast      -- cast function is from Data.Typeable

instance HasComponent AnyComponent where
    base (AnyComponent a) = base a
    downcast (AnyComponent a) = cast a

data Button = Button 
    {
       buttonComponent:: Component,   -- pointer to "superclass" info
       buttonText::Text
       -- other button attributes here
    } deriving Typeable

instance HasComponent Button where
    base = buttonComponent
    downcast = cast 

What solution is more idiomatic Haskell? Are there better solutions?

\$\endgroup\$
  • \$\begingroup\$ BTW, if you like mucking around with dynamic types, you might try Data.Dynamic, which builds atop Data.Typeable. \$\endgroup\$ – jpaugh Apr 9 '13 at 13:19
2
\$\begingroup\$

EDIT: Faithfully representing OO semantics in Haskell is currently impossible. In particular, method resolution can be simulated (but not easily, and not quite faithfully) via type classes and instances. This paper is very informative about current best practice, and possible future evolution.

Here is one possibility, which is similar to your first solution:

data ComponentClass a
    = ComponentClass {
        ...
        , comSubclass :: a
        ...
        }
type Component = ComponentClass ()

data ButtonClass a
    = ButtonClass {
        , buttonText :: Text
        , btSubclass :: a
        ...
        }
type Button = ComponentClass (ButtonClass ())

This is what I would do if I thought a simple hierachy fit my problem domain well (but not necessarily to mirror the OO in another language). However, this strategy does not work well if you need to track deep hierarchies, or multiple superclasses (such as in Python).

Another disadvantage is that you cannot deconstruct an arbitrary ComponentClass a into its subtype a. You can only do this if you know the type of a in advance. However, you could use dynamic typing to fix this issue, such as via Typable.

Note that you could also represent the hierachy in the up direction as well:

data ButtonClass a
    = ButtonClass {
          btText :: Text
        , btSuperClass :: a
        }
type Button = ButtonClass ComponentClass

You could then build each object "down the class hierarchy" until you discovered the most concrete type to wrap it up in.

Also note that Gtk2Hs uses a typeclassing strategy similar to your second one to represent the class hierachy of Gtk. Perhaps a look at its source code would be edicational, or even just browsing its documentation on Hackage.

Finally, the Exception hierachy implemented in GHC is quite interesting, though I don't think it would help you as much as either of the above---it's quite flat by comparison.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.