我想知道如何在功能语言中实现面向对象语言中常见的抽象工厂设计模式。特别是,我对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
答案 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语言通过子类化来实现它。