Haskell 哈斯克尔的抽象工厂

Haskell 哈斯克尔的抽象工厂,haskell,design-patterns,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 = Firs

我想知道如何用函数式语言实现抽象工厂设计模式,这种模式在面向对象语言中很常见。特别是,我对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
type类的任何实例都可以做到这一点,但显然不行

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

编辑

根据leftaroundabout的建议和解释,我提出了一个不同的解决方案,可以满足我的需求,而不会误用类型类。解决方案可能会有所改进,但目前这是我所能做到的最好的

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

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
正如我已经评论过的,整个想法都是徒劳的:你不需要Haskell中的抽象工厂。但除此之外,以下是您的特定尝试无法编译的原因


签名

  createProduct :: Product p => f -> p
意思不是你想的那样:在Java中,这会说“我将生成
产品
的子类的某个对象,但不要问是哪一个。”在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
类型实际上完全等同于
字符串
!通过同样的论证,
AnyFactory
将再次等同于此。所以基本上,你发布的整个代码都相当于

type Product = String

存在主义非常重要,你应该只在特殊情况下使用它们,而不是仅仅因为OO语言是通过子类化来实现的。

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


签名

  createProduct :: Product p => f -> p
意思不是你想的那样:在Java中,这会说“我将生成
产品
的子类的某个对象,但不要问是哪一个。”在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
类型实际上完全等同于
字符串
!通过同样的论证,
AnyFactory
将再次等同于此。所以基本上,你发布的整个代码都相当于

type Product = String


存在主义是相当复杂的,你应该只在特殊情况下使用它们,而不是仅仅因为OO语言是通过子类化来实现的。

抽象工厂模式或多或少是一种可笑的复杂方式来解决语言的能力问题,所以只需定义一个函数即可。你为什么在Haskell需要这个更客观的评论:Haskell类型的类对于OO语言中的类来说不是一个好的切入点。如果您想在Haskell中对抽象工厂之类的东西建模,您可能最好对“类”使用
data
。类型
createProduct
表示它可以创建
Product
的任何实例,而实现只能返回
FirstProduct
的实例。有关如何(不)创建的更多信息在Haskell中做OO,考虑,和。我可以使用<代码>数据>代码来定义工厂和产品集,但这将限制我的系统的可扩展性。如果我定义了
data Factory=FirstFactory | SecondFactory
,那么我的库仅限于这两种实现。相反,我想定义一个我可以依赖的
Factory
API,以便我的库的用户可以提供它的不同实现。您能解释一下如何使用函数来实现类似的目标吗?不,不,您只需定义一条记录:用于
产品
所需的所有内容,即所有接口方法的结果。(在您的示例中,这基本上只是一个
字符串
)。Haskell并不真正需要所有必须是派生类的字段或方法的东西:这种东西可以自动打包在fun的闭包中