Haskell的抽象工厂

frm*_*frm 0 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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

我想知道是否可以在Haskell中实现类似于抽象工厂的东西,或者我的方法是否完全错误.我可以使用其他"模式"来获得类似的结果吗?

编辑

根据该建议,并leftaroundabout的解释,我想出了一个不同的解决方案,满足了我的需求,又不滥用类型的类.解决方案可能会有所改进,但目前这是我能够实现的最佳解决方案.

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

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

productUser :: ProductSystem p -> String
productUser system = toString system $ create system
Run Code Online (Sandbox Code Playgroud)

图书馆的一些用户可以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"
Run Code Online (Sandbox Code Playgroud)

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

productUser firstSystem
productUser secondSystem
Run Code Online (Sandbox Code Playgroud)

lef*_*out 6

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


签名

  createProduct :: Product p => f -> p
Run Code Online (Sandbox Code Playgroud)

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

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

{-# LANGUAGE GADTs       #-}

data AnyProduct where
  AnyProduct :: Product p => p -> AnyProduct
Run Code Online (Sandbox Code Playgroud)

有了这个,你可以写

class Factory f where
  createProduct :: f -> AnyProduct
Run Code Online (Sandbox Code Playgroud)

对于某些人yourProduct :: AnyProduct,您可以通过toString这种方式"调用方法":

      ...
      productString = case yourProduct of
                        AnyProduct p -> toString p
      ...
Run Code Online (Sandbox Code Playgroud)

但是因为这实际上你可以用AnyProduct值做的唯一事情(比如在OO语言中,你不能访问未知子类的字段/方法),整个AnyProduct类型实际上完全等同于String单独!并且通过相同的论证,AnyFactory将再次等同于此.所以基本上,你发布的整个代码相当于

type Product = String
Run Code Online (Sandbox Code Playgroud)

...

存在通常是不受欢迎的,你应该只在特殊情况下使用它们,而不仅仅是因为OO语言通过子类化来实现它.