I was wondering how to implement the Abstract Factory design pattern, common in object-oriented languages, in a functional language. In particular, I am interested in an Haskell implementation.
I tried to implement the pattern using type classes:
class Product p where
toString :: p -> String
class Factory f where
createProduct :: Product p => f -> p
data FirstProduct = FirstProduct
data FirstFactory = FirstFactory
instance Product FirstProduct where
toString _ = "first product"
instance Factory FirstFactory where
createProduct _ = FirstProduct
When compiling this code, the following error is returned instead:
Could not deduce (p ~ FirstProduct)
from the context (Product p)
bound by the type signature for
createProduct :: Product p => FirstFactory -> p
at test.hs:14:3-15
‘p’ is a rigid type variable bound by
the type signature for
createProduct :: Product p => FirstFactory -> p
at test.hs:14:3
Relevant bindings include
createProduct :: FirstFactory -> p (bound at test.hs:14:3)
In the expression: FirstProduct
In an equation for ‘createProduct’: createProduct _ = FirstProduct
It looks like the compiler is not happy with the implementation of createProduct
, and in particular with its return value. I though that returning any instance of the Product
type class could do the trick, but it obviously doesn't.
I would like to know if something similar to an Abstract Factory can be implemented in Haskell or if my approach is completely wrong. Are there any other "patterns" I could use to achieve a similar result?
Edit
According to the suggestions and explanations of leftaroundabout, I came up with a different solution that fulfills my needs without misusing type classes. The solution could be probably be improved, but at the moment this is the best I was able to achieve.
The library would have to define something similar to the following:
data ProductSystem p = ProductSystem {create :: p, toString :: p -> String }
productUser :: ProductSystem p -> String
productUser system = toString system $ create system
Some users of the library could provide "implementations" of the ProductSystem
for their concrete needs:
data FirstProduct = FirstProduct
firstSystem :: ProductSystem FirstProduct
firstSystem = ProductSystem create toString
where
create = FirstProduct
toString p = "first"
data SecondProduct = SecondProduct
secondSystem :: ProductSystem SecondProduct
secondSystem = ProductSystem create toString
where
create = SecondProduct
toString p = "second"
Somewhere else, the two pieces could be unified to execute the wanted behavior:
productUser firstSystem
productUser secondSystem