I've read this question about the Abstract factory pattern, but the only answer to it tries to emulate in Haskell what one would in OOP languages (although the forword is along the lines you don't need it in Haskell).
On the other hand, my intention is not really to force an OOP-specific pattern on a Functional language as Haskell. Quite the opposite, I'd like to understand how Haskell addresses the needs that in OOP are addressed via the Factory pattern.
I have the feeling that even these needs might not make sense in Haskell in the first place, but I can't formulate the question any better.
My understanding of the structure of the factory pattern (based on this video which seems pretty clear) is that
- there's a bunch of different products, all implementing a common interface
- there's a bunch of different creator classes, all implementing a common interface
- each of the classes in 2 hides a logic to create a product of those in 1
- (if I understand correctly, it's not necessary that there's a 1-to-1 mapping between objects and creators, as the creators could just hide different logics make different choices about which specific object to create based on user input/time/conditions/whatever.)
- the client code will have the creators at its disposal, and everytime one of those is used it (the creator, not the client code) will know how to crate the product and which specific product.
How does all (or some) of this apply to Haskell?
Having several different product classes implementing a common product interface is a thing in Haskell, the interface being a typeclass
, and the products being types (data
s/newtype
s/existing types). For instance, with reference to the spaceship-and-asteroids example from the linked video, we could have a typeclass
for defining Obstacles
anything that provides size
, speed
, and position
,
class Obstacle a where
size :: a -> Double
speed :: a -> Int
position :: a -> (Int,Int)
and Asteroid
and Planet
could be two concrete types implementing this interface in some way,
data Asteroid = Asteroid { eqside :: Double, pos :: (Int,Int) } deriving Show
instance Obstacle Asteroid where
size a = eqside a ^ 3 -- yeah, cubic asteroids
speed _ = 100
position = pos
data Planet = Planet { radius :: Double, center :: (Int,Int) } deriving Show
instance Obstacle Planet where
size a = k * radius a ^ 3
where k = 4.0/3.0*3.14
speed _ = 10
position = center
So far I don't see any real difference between what I'd do in Haskell or in a OOP language. But it comes next.
At this point, following the example in the linked video, the client code could be a game that traverses some levels and generates different obstacles based on the number of the level; it could be something like this:
clientCode :: [Int] -> IO ()
clientCode levels = do
mapM_ (print . makeObstacle) levels
where makeObstacle
should be the creator function, or one of several, which given an input of type Int
applies a logic to chose if it has to create an Asteroid
or a Planet
.
However I don't see how I can have a function that returns outputs of different types, Asteroid
vs Planet
(the fact they implement the same Obstacle
interface doesn't seem to help), based on different values all of the same type, [Int]
, let alone understanding what the "factory" functions and their common interface should be.
Aucun commentaire:
Enregistrer un commentaire