1

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
frm
  • 3,346
  • 1
  • 21
  • 21
  • 11
    The abstract factory pattern is more or less a ridiculously convoluted way of working yourself around a language's inability so simply define **a function**. Why would you need that in Haskell? — More objective remark: Haskell type classes are _not_ a good drop-in for something that's a class in an OO language. If you want to model something like Abstract Factory in Haskell, you'd probably better use `data` for the "classes". – leftaroundabout Jan 20 '15 at 12:47
  • 1
    The type of `createProduct` indicates it can create any instance of `Product` while the implementation can only return instances of `FirstProduct`. – Lee Jan 20 '15 at 12:54
  • 1
    For more info on how to (not) do OO in Haskell, consider https://www.haskell.org/haskellwiki/OOP_vs_type_classes, http://stackoverflow.com/questions/20184286/object-oriented-programming-in-haskell/20188103, and https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/. – leftaroundabout Jan 20 '15 at 12:57
  • I could use `data` to define sets of factories and products, but this would limit the extensibility of my system. If I define `data Factory = FirstFactory | SecondFactory`, then my library is only limited to those two implementations. I would like instead to define a `Factory` API I could rely on, so that users of my library could provide different implementations of it. Can you explain how to use functions to achieve a similar goal? – frm Jan 20 '15 at 12:57
  • 1
    No no, you just define one record: for all that's _needed_ from `Product`, i.e. for the results of all its _interface methods_. (In your example, this is basically just a single `String`). Everything that would have to be fields or methods of derived classes isn't really needed in Haskell: that kind of stuff can be automatically packed in closures of the functions ("factories") with which you generate your `Product`s. It's actually a lot like the "ad-hoc-object-orientation" that some dynamic languages offer. – leftaroundabout Jan 20 '15 at 13:02

1 Answers1

7

As I already commented, the whole idea is futile: you don't need abstract factories in Haskell. But that aside, here's why your particular attempt doesn't compile.


The signature

  createProduct :: Product p => f -> p

means not what you apparently think: in Java, this would say "I'll generate some object of a subclass of Product, don't ask which though." In Haskell – whose subclasses are not subtypes! – this means, "for any (specific!) instance of Product that you request, I'll give you an object of that concrete type." Which isn't possible, since FirstFactory apparently can't supply a SecondProduct.

To express what you tried to say, you need an explicit wrapper to contain "any subclass". We call that an existential, it's written thus:

{-# LANGUAGE GADTs       #-}

data AnyProduct where
  AnyProduct :: Product p => p -> AnyProduct

with this, you can write

class Factory f where
  createProduct :: f -> AnyProduct

For some yourProduct :: AnyProduct, you can then "call the toString method" this way:

      ...
      productString = case yourProduct of
                        AnyProduct p -> toString p
      ...

But since this is actually the only thing you can do with an AnyProduct value (like in OO language, you can't access fields/methods of the unknown subclass), the whole AnyProduct type is actually completely equivalent to String alone! And by the same argument, AnyFactory would again be equivalent to that. So basically, the entire code you posted is equivalent to

type Product = String

...

Existentials are quite generally frowned upon, you should only use them in special cases, not for anything just because OO languages do it with subclassing.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • This seems interesting, but as you stated before my problem could be solved with much simpler constructs. I edited my question to add another solution that came to my mind. Anyway, today I definitely learnt something! – frm Jan 20 '15 at 13:54