oophaskelldesign-patternsfunctional-programmingfactory-pattern

Why is the Factory pattern not needed in Haskell? And how are the needs that pattern addresses in OOP addressed in Haskell?


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 foreword 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

  1. there's a bunch of different products, all implementing a common interface
  2. there's a bunch of different creator classes, all implementing a common interface

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 (datas/newtypes/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.


Solution

  • Having several different product classes implementing a common product interface is a thing in Haskell, the interface being a typeclass

    Not quite. It's true that typeclasses can express what interfaces do in OO languages, but this doesn't always make sense. Specifically, there's not really any point to a class where all methods have type signatures of the form a -> Fubar.

    Why? Well, you don't need a class for that – just make it a concrete data type!

    data Obstacle = Obstace
      { size :: Double
      , speed :: Int      -- BTW, why would speed be integral??
      , position :: (Int,Int) }
    

    The record fields can also be functions, IO actions, etc. etc. – which is enough to emulate what the methods of an OO class can do. The only thing plain data can't express is inheritance – but even in OO, there's a bit of a mantra about that composition should be preferred over inheritance, so there's that.

    Alternatively, you could make Obstacle a sum type

    data Obstacle = Asteroid ...
                  | Planet ...
    

    It depends on the application which is better. Either way it's still a concrete data type, no class required.

    With Obstacle being a simple data type, there's nothing that needs to be “abstract” about it's creators. Instead, you can simply have various functions -> Obstacle that create obstacles which happen to represent either asteroids, planets, or whatever.

    You can also wrap your kind of “OO interface instances” into a data type, an existential

    {-# LANGUAGE GADTs #-}
    
    class Obstacle a where ...
    
    data AnObstacle where
      AnObstance :: Obstacle a => a -> AnObstacle
    

    But don't do that unless you exactly know this is what you want.