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