Haskell 中的抽象工厂

Abstract factories in Haskell

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

我尝试使用类型 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

编译此代码时,return编辑了以下错误:

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 的实现不满意,尤其是它的 return 值。我虽然 returning Product 类型 class 的任何实例都可以做到这一点,但显然不行。

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

编辑

根据 leftaroundabout 的建议和解释,我想出了一个不同的解决方案,可以满足我的需求,而不会滥用类型 classes。该解决方案可能会得到改进,但目前这是我能够实现的最好结果。

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

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

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

图书馆的一些用户可以提供 "implementations" 的 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

正如我已经评论过的,整个想法是徒劳的:你不需要 Haskell 中的抽象工厂。但除此之外,这就是您的特定尝试无法编译的原因。


签名

  createProduct :: Product p => f -> p

的意思不是你显然认为的那样:在 Java 中,这会说 "I'll generate some object of a subclass of Product, don't ask which though." 在 Haskell 中 – 其子类是 而不是 子类型! – 这意味着,"for any (specific!) instance of Product that you request, I'll give you an object of that concrete type." 这是不可能的,因为 FirstFactory 显然无法提供 SecondProduct.

为了表达你想表达的意思,你需要一个明确的包装器来包含 "any subclass"。我们称之为存在,它是这样写的:

{-# LANGUAGE GADTs       #-}

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

有了这个,你就可以写

class Factory f where
  createProduct :: f -> AnyProduct

对于某些 yourProduct :: AnyProduct,您可以 "call the toString method" 这样:

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

但是因为这实际上是 唯一的事情 你可以使用 AnyProduct 值(就像在 OO 语言中一样,你不能访问 fields/methods未知子类的),整个 AnyProduct 类型实际上完全等同于单独的 String!通过同样的论证,AnyFactory 将再次等同于此。所以基本上,您发布的整个代码相当于

type Product = String

...

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