Haskell的抽象工厂

时间:2015-01-20 12:43:27

标签: haskell design-patterns

我想知道如何在功能语言中实现面向对象语言中常见的抽象工厂设计模式。特别是,我对Haskell实现很感兴趣。

我尝试使用类型类实现模式:

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

编译此代码时,会返回以下错误:

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

看起来编译器对createProduct的实现不满意,尤其是返回值。我虽然返回Product类型类的任何实例都可以做到这一点,但它显然没有。

我想知道是否可以在Haskell中实现类似于抽象工厂的东西,或者我的方法是否完全错误。还有其他"模式"我可以用来达到类似的效果吗?

修改

根据左下角的建议和解释,我提出了一个不同的解决方案,可以满足我的需求,而不会滥用类型类。解决方案可能会有所改进,但目前这是我能够实现的最佳解决方案。

库必须定义类似于以下内容的内容:

data ProductSystem p = ProductSystem {create :: p, toString :: p -> String }

productUser :: ProductSystem p -> String
productUser system = toString system $ create system

图书馆的一些用户可以提供"实施" ProductSystem的具体需求:

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"

在其他地方,这两个部分可以统一起来执行想要的行为:

productUser firstSystem
productUser secondSystem

1 个答案:

答案 0 :(得分:6)

正如我已经评论过的那样,整个想法都是徒劳的:你不需要在Haskell中使用抽象工厂。但除此之外,这就是为什么你的特定尝试不能编译的原因。


签名

  createProduct :: Product p => f -> p

意味着你没有想到的东西:在Java中,这会说“我会生成一些Product子类的对象,但不要问哪个。”在Haskell中 - 其子类是不是子类型! - 这意味着,“对于你要求的任何(特定!)Product实例,我会给你一个具体类型的对象。”这是不可能的,因为FirstFactory显然无法提供SecondProduct

要表达您想说的内容,您需要一个显式包装器来包含“任何子类”。我们称之为存在主义,因此写成:

{-# LANGUAGE GADTs       #-}

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

有了这个,你可以写

class Factory f where
  createProduct :: f -> AnyProduct

对于某些yourProduct :: AnyProduct,您可以“以这种方式”调用toString方法:

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

但是,由于这实际上只是你可以使用AnyProduct值做的唯一(就像在OO语言中一样,你无法访问未知子类的字段/方法),整个AnyProduct类型实际上完全等同于String!通过相同的论证,AnyFactory将再次等同于此。基本上,您发布的整个代码等同于

type Product = String

...

Existentials非常generally frowned upon,你应该只在特殊情况下使用它们,而不仅仅是因为OO语言通过子类化来实现它。