Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid OOP deep class hierarchies when switching to Haskell?

Tags:

haskell

A game written in say C++ typically has a class hierarchy such as

  • CEntity
    • CMoveable
      • CCar
      • CTank
      • CJetPack
    • CHuman
      • CPedestrian
      • CPlayer
      • CAlien
    • CRigid
      • CRock
      • CGrenade
    • CMissile
    • CGun
    • CMedkit

Now I have read that some have argued that a class hierarchy is a wrong architecture even when using C++. But at least it attempts code reuse. And is the obvious way for being able to shove everything into one managing container since everything trivially fits into a list of CEntity.

But in any case, for someone trying to switch from C++ to Haskell for making games how does one change the architecture to fit within Haskell's functional paradigm?

like image 648
user782220 Avatar asked Feb 11 '14 09:02

user782220


People also ask

What potential problems can you encounter when you have a very deep class hierarchy 6 classes?

The major issues here are mundane: Fragile base classes (changes to base are a nightmare for the derived) Increased coupling (with too many base classes comes tight coupling) Encapsulation weakens.

Is Oops outdated?

OOP is not outdated.

Is Haskell object oriented?

Haskell isn't an object-oriented language. All of the functionality built here from scratch already exists in a much more powerful form, using Haskell's type system.

Why is object oriented programming so hard?

As a beginner, OOP is also more difficult to read for several non-code related reasons. First, it's near impossible to understand why a piece of code exists if you're unfamiliar with the domain being modeled with classes. Secondly, OOP is a craft and is inherently opinionated.


2 Answers

I'd argue it's a mistake to translate OO code into Haskell, but instead write your game from the ground up in Haskell.

In my opinion the most appropriate tool to use for games programming is Functional Reactive Programming. This makes you think in terms of Behaviours and Events - your game elements change over time and you combine them and define relationships between them.

(You don't need to shove everything into a single container unless you're missing an advanced way of managing world updates and are forced to iterate along some collection applying a .update() method. Functional Reactive Programming is an advanced way of managing updates.)

It takes time to learn to think FRP, but the investment is worth it.

Code reuse is (as usual in Haskell) through

  • very general type signatures - polymorphic or typeclass based
  • higher order functions
  • great abstractions like Functor, Applicative, Foldable, Traversable, Monad
  • avoiding needlessly making impure code - pure code is easiest to combine and reapply
  • spotting similarities in how things behave or combine - don't write the same code twice
  • Scrap Your Boilerplate
  • Template Haskell

these tend to apply much more widely than subtype polymorphism.

like image 80
not my job Avatar answered Oct 07 '22 14:10

not my job


Let me first say that I don't know anything about game development, so my answer might well not apply to your question.

That being said, I think the key is to ask the question: why do you use a class hierachy in a language like C++? I think the answer is two-fold: subtype-polymorphism and code reuse.

As you have noted, using inheritance to achieve code-reuse is often criticized, and I believe rightly so. Prefer composition to inheritance is often good advice, it reduces coupling and makes things more explicit. The equivalent in Haskell is just to reuse functions, which is quite simple.

That leaves us with the second benefit: subtype-polymorphism. Haskell does not support subtypes or subtype polymorphsim, but with typeclasses it has another kind of ad-hoc polymorphism which is even more general (in that things don't need to be in a sub-type relationship to implement the same functions).

So my answer is: Think about why you would want a class hierachy. If you want code reuse, then just reuse code by factoring it into sufficiently general functions and reuse these, if you want polymorphism use type classes.

There are some instances where subtyping is actually useful and thus it is sometimes a drawback that Haskell does not support this, but in my experience this is quite rare. On the other hand inheritance tends to be overused in languages such as C++ or Java because that is the one-size-fits-all tool they provide.

In general, I agree with @enoughreptocomment's answer, namely that it is a mistake to reproduce OO designs in Haskell -- you can usually do much better! I was just trying to point out the things that class hierachies give you and how similar things can be achieved in Haskell.

Edit (in response to Zeta's comment):

It's true that typeclasses don't allow heterogeneous types in data types such as lists, however with an extra helper data type this can also be achieved (stolen from the Haskell wikibook):

{-# LANGUAGE ExistentialQuantification #-}

data ShowBox = forall s. Show s => SB s

heteroList :: [ShowBox]
heteroList = [SB (), SB 5, SB True]

instance Show ShowBox where
  show (SB s) = show s

f :: [ShowBox] -> IO ()
f xs = mapM_ print xs

main = f heteroList
like image 41
Paul Avatar answered Oct 07 '22 15:10

Paul