Haskell 哈斯克尔的抽象工厂
我想知道如何用函数式语言实现抽象工厂设计模式,这种模式在面向对象语言中很常见。特别是,我对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
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的闭包中