A game written in say C++ typically has a class hierarchy such as
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?
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.
OOP is not outdated.
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.
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.
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
Functor
, Applicative
, Foldable
, Traversable
, Monad
these tend to apply much more widely than subtype polymorphism.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With