How do I model inheritance in Haskell? - haskell

I am attempting to create a game engine that is composed of a few different types:
data Camera = Camera ...
data Light = SpotLight ... | DirectionalLight ...
data Object = Monster ... | Player ... | NPC ...
However, I'm now trying to implement basic physics for all of these entities. This requires that they each contain a pos :: (Double, Double, Double) and a velocity :: (Double, Double, Double).
In object oriented languages, I would implement it as something like:
Camera implements PhysicalObject
where PhysicalObject contains the two properties pos and velocity.
My immediate reaction was to place them all in the same type:
data Object = Monster ... | Player ... | NPC ... | Camera ...
However, I fear that this might make it tough to implement camera-specific functions, light-specific functions, etc. In reality, they have very little else in common other than the fact that they all possess a physical position and velocity in the world.
Is there a simpler way to do this than defining the two properties inside each type constructor?

I can think of two approaches - type classes and lenses.
Type classes
class PhysicalObject m where
position :: m -> (Double, Double, Double)
velocity :: m -> (Double, Double, Double)
You would then make instances for the objects along the following lines
data Camera = Camera
{ cameraPosition :: (Double,Double,Double)
, cameraVelocity :: (Double,Double,Double)
}
instance PhysicalObject Camera where
position = cameraPosition
cameraVelocity = cameraVelocity
and similarly for your other types. Then any function which doesn't need to know the details of an object can just require its arguments to be instances of PhysicalObject, for example:
type TimeInterval = Double
newPosition :: PhysicalObject m => TimeInterval -> m -> (Double,Double,Double)
newPosition dt obj = (x + du * dt, y + dv * dt, z + dw * dt)
where
(x,y,z) = position obj
(u,v,w) = velocity obj
However, you will struggle to write functions which modify your objects using this code - the class tells Haskell how it can access the position and velocity of an object, but not how to modify them.
Lenses
The other option is to turn to the lens library. This is a bit of a beast to being with, but it allows you to write some very natural code. First, there's a bit of boilerplate
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
Now define some position and velocity data types. Don't worry about the weird field names prefixed with underscores - we won't be using them.
data Pos = Pos { _posX, _posY, _posZ :: Double }
data Vel = Vel { _velX, _velY, _velZ :: Double }
instance Show Pos where show (Pos x y z) = show (x,y,z)
instance Show Vel where show (Vel x y z) = show (x,y,z)
Now you use a bit of Template Haskell to derive lenses for your data types. This will generate type classes HasPos and HasVel whose methods allow you to access and modify any value that is an instance of those classes.
makeClassy ''Pos
makeClassy ''Vel
Now define your camera class, which includes a position and a velocity.
data Camera = Camera
{ _cameraPos :: Pos
, _cameraVel :: Vel } deriving (Show)
Another bit of Template Haskell will automatically create functions cameraPos and cameraVel that allow you to access and modify the position and velocity of your camera.
makeLenses ''Camera
Finally, declare that your camera is an instance of both the HasPos and HasVel classes, with a default implementation of their methods.
instance HasPos Camera where pos = cameraPos
instance HasVel Camera where vel = cameraVel
Now we're ready to do some real work. Let's define an example camera
camera = Camera (Pos 0 0 0) (Vel 10 5 0)
A function to modify the camera, returning a new one with an updated position, is
move :: (HasPos a, HasVel a) => TimeInterval -> a -> a
move dt obj = obj
& posX +~ dt * obj^.velX
& posY +~ dt * obj^.velY
& posZ +~ dt * obj^.velZ
Note that this is a completely generic function for moving any object that has a position and velocity - it's not at all specific to the Camera type. It also has the advantage of looking a lot like imperative code!
If you now load all this into GHCI, you can see it in action
>> camera
Camera {_cameraPos = (0.0,0.0,0.0), _cameraVel = (10.0,5.0,0.0)}
>> move 0.1 camera
Camera {_cameraPos = (1.0,0.5,0.0), _cameraVel = (10.0,5.0,0.0)}

I would implement it similar to:
type Position = (Double, Double, Double)
type Velocity = (Double, Double, Double)
class PhysicalObject a where
pos :: a -> Position
velocity :: a -> Velocity
data Camera = Camera
{ camPos :: Position
, camVel :: Velocity
} deriving (Eq, Show)
instance PhysicalObject Camera where
pos = camPos
velocity = camVel
Then you can do similarly for each type you define that needs PhysicalObject.

You'll want to begin to depend on things like typeclasses and object encodings. The first method is to encode the common interface as a typeclass each type inherits from.
class PhysicalObject o where
pos :: o -> Vector3
velocity :: o -> Vector3
The second is to build a common object
data PhysicalObject = PhysicalObject { poPos :: Vector3, poVelocity :: Vector3 }
data Monster = Monster { monsterPO :: PhysicalObject
, ... monsterStuff ...
}
which could even be used to instantiate the first typeclass
instance PhysicalObject PhysicalObject where
pos = poPos
velocity = poVelocity
instance PhysicalObject Monster where
pos = pos . monsterPO
velocity = velocity . monsterPO
Be careful with typeclass encodings like this, though, as too great a use of them often causes ambiguity when reading code. It can be difficult to understand the types and know which instance is being used.

Related

Haskell inheritance, data, constructors

So I want to define multiple data classes for my Asteroids game/assignment:
data One = One {oneVelocity :: Velocity, onePosition :: Position, (((other properties unique to One)))}
data Two = Two {twoVelocity :: Velocity, twoPosition :: Position, (((other properties unique to Two)))}
data Three = Three {threeVelocity :: Velocity, threePosition :: Position, (((other properties unique to Three)))}
As you can see I have multiple data classes with some overlapping properties (velocity, position). That also meant that I had to give them different names per data class ("oneVelocity", "twoVelocity", ...).
Is there a way I can let these types of data extend something? I thought of using one datatype with multiple constructors, but some of these current data classes are very different and I don't thing they should reside in one data class with multiple constructors.
You should probably use just a single data type for all of these, but parameterised on the specific details:
data MovingObj s = MovingObj
{ velocity :: Velocity
, position :: Position
, specifics :: s }
Then you can create e.g. asteroid :: MovingObj AsteroidSpecifics, but you can also write functions that work with any such moving object like
advance :: TimeStep -> MovingObj s -> MovingObj s
advance h (MovingObj v p s) = MovingObj v (p .+^ h*^v) s
There is no inheritance in Haskell (at least, not the kind you associate with object-oriented classes). You just want composition of data types.
data Particle = Particle { velocity :: Velocity
, position :: Position
}
-- Exercise for the reader: research the GHC extension that
-- allows all three of these types to use the same name `p`
-- for the particle field.
data One = One { p1 :: Particle
, ... }
data Two = Two { p2 :: Particle
, ... }
data Three = Three { p3 :: Particle
, ... }
Or, you can define a type that encapsulates the other properties, and let those be added to different kinds of Particles.
data Properties = One { ... }
| Two { ... }
| Three { ... }
data Particle = Particle { velocity :: Velocity
, position :: Position
, properties :: Properties
}
(Or see #leftaroundabout's answer, which is a nicer way of handling this approach.)

Getting Haskell to Distinguish Type Synonyms

I like the idea of Haskell type synonyms, because they allow for distinguishing between abstract datatypes that share underlying representations. Unfortunately, when I write a program like
data Vector a = Vec a a
-- Some definitions here about (+) and (*) for Vector ...
type Position = Vector Float
type Velocity = Vector Float
type Time = Float
step :: Position -> Velocity -> Time -> Position
step p v dt = p + v*dt
p :: Position
p = Vec 0.0 0.0
v :: Velocity
v = Vec 1.0 1.0
p' = step v p 0.01
This is perfectly valid Haskell code, despite v and p being in the wrong spots. I would like to strengthen the distinction between type synonyms, such that they still share underlying representation, but are not accepted as each other in function application. Is this possible?
You could make Vector a phantom type as follows:
data Vector t a = Vec a a
data Pos
data Vel
type Position = Vector Pos Float
type Velocity = Vector Vel Float
Now, you can define instances of Position and Velocity like you'd normally do:
p :: Position
p = Vec 0.0 0.0
v :: Velocity
v = Vec 1.0 1.0
However, it won't allow you to use them interchangeably:
type Time = Float
step :: Position -> Velocity -> Time -> Position
step p v dt = p + v*dt -- you might have to change this definition
p' = step v p 0.01 -- won't compile
You can also make things more precise by using DataKinds and KindSignatures:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
data VectorType = Pos | Vel
data Vector (t :: VectorType) a = Vec a a
type Position = Vector Pos Float
type Velocity = Vector Vel Float
Hope that helps.
newtype is likely what you want, or at any rate, the best we've got. Like type, it defines a new name for an existing type, and the runtime representation will be the same. Unlike type (but like data), they are considered different during type checking, and there is a new data constructor.
So you might have code like:
newtype Position = Position (Vector Float)
p :: Position
p = Position (Vec 0 0)

In Haskell how can I override the (==) and (/=) operators for a type class?

Say I have something like this
class Circle c where
x :: c -> Float
y :: c -> Float
radius :: c -> Float
data Location = Location { locationX :: Float
, locationY :: Float
} deriving (Show, Eq)
data Blob = Location { blobX :: Float
, blobY :: Float
, blobRadius :: Float,
, blobRating :: Int
} deriving (Show, Eq)
instance Circle Location where
x = locationX
y = locationY
radius = pure 0
instance Circle Blob where
x = blobX
y = blobY
radius = blobRadius
Say for example I want Circle types to be equal if their x and y points are equal. How can I compare instances of the type class with the (==) and (/=) operators. I know I can do something like this, but is it possible to overload the operators?
equal :: Circle a => Circle b => a -> b -> Bool
equal a b = (x a == x b && y a == y b)
I want to be able to compare with
(Location 5.0 5.0) == (Blob 5.0 5.0 ... ) should give me True
Zeroth, some standard imports:
import Data.Function (on)
import Control.Arrow ((&&&))
First, this is not a good idea. a==b should only be true if a and b are (for all purposes relevant to the user) interchangeable – that's clearly not the case for two circles which merely happen to share the same center point!
Second, it's probably not a good idea to make Circle a typeclass in the first place. A typeclass only makes sense when you want to abstract over something that can't directly be expressed with just a parameter. But if you just want to attach different “payloads” to points in space, a more sensible approach might be to define something like
data Located a = Located {x,y :: ℝ, payload :: a}
If, as seems to be the case, you actually want to allow different instances of Circle to coexist and be comparable at runtime, then a typeclass is entirely the wrong choice. That would be an OO class. Haskell doesn't have any built-in notion of those, but you could just use
data Blob = Blob
{ x,y :: ℝ
, radius :: ℝ
, rating :: Maybe Int }
and no other types.
https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/
Third, the instance that you asked for can, theoretically speaking, be defined as
instance (Circle a) => Eq a where
(==) = (==)`on`(x &&& y)
But this would be a truely horrible idea. It would be a catch-all instance: whenever you compare anything, the compiler would check “is it of the form a?” (literally anything is of that form) “oh great, then said instance tells me how to compare this.” Only later would it look at the Circle requirement.
The correct solution is to not define any such Eq instance at all. Your types already have Eq instances individually, that should generally be the right thing to use – no need to express it through the Circle class at all, just give any function which needs to do such comparisons the constraint (Circle a, Eq a) => ....
Of course, these instances would then not just compare the location but the entire data, which, as I said, is a good thing. If you actually want to compare only part of the structure, well, make that explicit! Use not == itself, but extract the relevant parts and compare those. A useful helper for this could be
location :: Circle a => a -> Location
location c = Location (x c) (y c)
...then you can, for any Circle type, simply write (==)`on`location instead of (==), to disregard any other information except the location. Or write out (==)`on`(x &&& y) directly, which can easily be tweaked to other situations.
Two circles that share a common center aren't necessarily equal, but they are concentric; that's what you should write a function to check.
concentric :: (Circle a, Circle b) => a -> b -> Bool
concentric c1 c2 = x c1 == x c2 && y c1 == y c2

Iterating over custom data types in Haskell

I have a custom data type that looks like this:
data Circle = Circle
{ radius :: Float
, xPosition :: Float
, yPosition :: Float
}
I want to be able to write a scale function that can take a given circle and change its size like this:
aCircle = Circle 1.5 1 1
scaleFn aCircle 10
The desired output for this example with scale of 10 would be:
Circle 15 10 10
How can I create a function where I can iterate over each field and multiple the values by a constant? In my actual use case I need a way to map over all the fields as there are many of them.
Scaling by a factor is generally a vector space operation. You could do the following:
{-# LANGUAGE TypeFamilies, DeriveGeneric #-}
import Data.VectorSpace
import GHC.Generics (Generic)
data Circle = Circle
{ radius :: Float
, xPosition :: Float
, yPosition :: Float
} deriving (Generic, Show)
instance AdditiveGroup Circle
instance VectorSpace Circle where
type Scalar Circle = Float
main = print $ Circle 1.5 1 1 ^* 10
(result: Circle {radius = 15.0, xPosition = 10.0, yPosition = 10.0}).
(requires vector-space >= 0.11, which has just added support for generic-derived instances.)
However I should remark that Circle as such is not really a good VectorSpace instance: adding two circles doesn't make any sense, and scaling by a negative factor gives a bogus radius. Only define such an instance if your real use case follows the actual vector space axioms.
What you really want for a type like Circle is something like diagrams' Transformable class. But I don't think there's any automatic way to derive an instance for that. In fact, since diagrams has – unfortunately IMO – switched from vector-space to linear, something like this has become considerably tougher to do even in principle.
You can use "scrap your boilerplate":
import Data.Generics
data Circle = Circle
{ radius :: Float
, xPosition :: Float
, yPosition :: Float
}
deriving (Show, Data)
circleModify :: (Float -> Float) -> Circle -> Circle
circleModify f = gmapT (mkT f)
Intuitively, above, mkT f transforms f into a function which is applicable to any type: if the argument of mkT f is a Float, then f is applied, otherwise the argument is returned as it is.
The newly constructed general function is called a "transformation": the T in mkT stands for that.
Then, gmapT applies the transformation mkT f to all the fields of the circle. Note that is a field contained, say, (Float, Bool) that float would be unaffected. Use everywhere instead of gmapT to recursively go deeper.
Note that I'm not a big fan of this approach. If for any reason you change the type of a field, that change will not trigger a type error but gmapT (mkT ...) will now simply skip over that field.
Generic programming can be convenient, but sometimes a bit too much, in that type errors can be silently transformed into unexpected results at runtime. Use with care.

Haskell Gloss Particle Effects

How to create particle effects in Haskell using the Gloss library? (e.g. to show an explosion)
If anyone could help me out a bit on how this is done it'd be much appreciated.
Best Regards,
Skyfe.
The comment on the question does a good job of providing a high-level solution, but I'm writing this answer to add detail.
Let's start by modeling the real-world object we want to represent. In our case, it's a particle. A particle ought to have a position, a velocity and an acceleration, all of which we can represent using 2D vectors. A reasonable way to store 2D vectors in Haskell is to use the Linear.V2 module. Next, let's think about nice additional properties we'd like a particle should have, specifically one involved in a firework or explosion. Notice how the particles in a firework burn bright for a time and then just 'fizzle out'? Let's call said time the particle's lifespan, and represent it using a Float. We can now create an appropriate representation for a Particle and a Cluster of Particles
data Particle = Particle
{ _age :: Float
, _lifespan :: Float
, _position :: V2 Float
, _velocity :: V2 Float
, _acceleration :: V2 Float }
deriving ( Show )
type Cluster = [Particle]
makeLenses ''Particle
There's an extra field called age in our datatype above. The lifespan of the particle represents the time for which the particle exists from creation to death, while its age represents the time that has passed since the Particle's creation. In other words, a Particle should disappear when its age exceeds its lifespan. Keep that in mind for later.
Next, let's write a function that helps us create a Particle. All it does is set the initial age to 0 and leave the rest up to additional arguments
makeParticle :: Float -> V2 Float -> V2 Float -> V2 Float -> Particle
makeParticle = Particle 0
Once this is done, we can write a function that helps us create a Cluster of n particles
makeCluster :: Int -> (Int -> Particle) -> Cluster
makeCluster n particleGen = map particleGen [0..(n - 1)]
After that, we create a function that will allow us to advance a Particle by dt seconds. The function advances the Particle's
age, changes its position based on its velocity and finally changes its velocity based on its acceleration. In the end, if the age of the Particle is more than its lifespan, we symbolize the deletion of the Particle by evaluating to Nothing instead of Just the changed particle.
advanceParticle :: Float -> Particle -> Maybe Particle
advanceParticle dt = hasDecayed . updateVel . updatePos . updateAge
where
r2f = realToFrac
hasDecayed p = if p^.age < p^.lifespan then Just p else Nothing
updateAge p = (age %~ (dt +)) p
updatePos p = (position %~ (r2f dt * p^.velocity +)) p
updateVel p = (velocity %~ (r2f dt * p^.acceleration +)) p
The following function advances a Cluster, and gets rid of 'dead' Particles
advanceCluster :: Float -> Cluster -> Cluster
advanceCluster dt = catMaybes . map (advanceParticle dt)
Now we can move on to the part of the code that has to do with actually drawing particles using Graphics.Gloss. We're going to use a Cluster to represent the state of the simulation, and so we start with a function that returns a Cluster representing the initial state of the program. For a simple animation we're going to simulate a firework, where all the particles start in the same position, have the same lifespan, radiate out from their central position at regular angles, and are subject to the same acceleration
initState :: Cluster
initState = makeCluster numParticles particleGen
where
numParticles = 10
particleGen :: Int -> Particle
particleGen i =
makeParticle initLifespan
initPosition
(initVelMagnitude * V2 (cos angle) (sin angle))
initAcceleration
where
fI = fromIntegral
angle = (fI i) * 2 * pi / (fI numParticles)
initLifespan = 10
initPosition = V2 0 0
initVelMagnitude = 5
initAcceleration = V2 0 (-3)
Then we write a function to draw a Cluster on to the screen
drawState :: Cluster -> Picture
drawState = pictures . map drawParticle
where
drawParticle :: Particle -> Picture
drawParticle p =
translate (p^.position._x) (p^.position._y) .
color (colorAdjust (p^.age / p^.lifespan)) .
circleSolid $ circleRadius
where
circleRadius = 3
colorAdjust a = makeColor 1 0 0 (1 - a)
Probably the only non-standard part about this is the colorAdjust function. What I was going for here was to color a Particle red and when it's created have it not be transparent at all (i.e. alpha value of 1) and keep fading out as its age approaches its lifespan (i.e. alpha value that keeps approaching 0)
We're almost done! Add a function that updates the Cluster to reflect the passage of time
stepState :: ViewPort -> Float -> Cluster -> Cluster
stepState _ = advanceCluster
Finish up the program by writing a main function that ties everything together
main :: IO ()
main =
simulate (InWindow name (windowWidth, windowHeight)
(windowLocX, windowLocY))
bgColor
stepsPerSec
initState
drawState
stepState
where
name = "Fireworks!"
windowWidth = 300
windowHeight = 300
windowLocX = 30
windowLocY = 30
stepsPerSec = 30
bgColor = white
I hope this helps!

Resources