哈斯克尔的抽象工厂 [英] Abstract factories in Haskell

查看:71
本文介绍了哈斯克尔的抽象工厂的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道如何以一种功能语言实现面向对象语言中常见的Abstract Factory设计模式.我尤其对Haskell实现感兴趣.

I was wondering how to implement the Abstract Factory design pattern, common in object-oriented languages, in a functional language. In particular, I am interested in an Haskell implementation.

我试图使用类型类来实现模式:

I tried to implement the pattern using type 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

编译此代码时,将返回以下错误:

When compiling this code, the following error is returned instead:

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 类型类的任何实例都可以解决问题,但显然没有.

It looks like the compiler is not happy with the implementation of createProduct, and in particular with its return value. I though that returning any instance of the Product type class could do the trick, but it obviously doesn't.

我想知道在Haskell中是否可以实现类似于Abstract Factory的东西,或者我的方法是否完全错误.还有其他我可以用来达到类似结果的模式"吗?

I would like to know if something similar to an Abstract Factory can be implemented in Haskell or if my approach is completely wrong. Are there any other "patterns" I could use to achieve a similar result?

修改

根据关于"leftaboutabout"的建议和解释,我想出了另一种解决方案,可以满足我的需求而不会滥用类型类.该解决方案可能会得到改进,但是目前这是我所能达到的最佳效果.

According to the suggestions and explanations of leftaroundabout, I came up with a different solution that fulfills my needs without misusing type classes. The solution could be probably be improved, but at the moment this is the best I was able to achieve.

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

The library would have to define something similar to the following:

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

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

该库的某些用户可以为他们的具体需求提供 ProductSystem 的实现":

Some users of the library could provide "implementations" of the ProductSystem for their concrete needs:

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"

在其他地方,可以将这两部分统一起来执行所需的行为:

Somewhere else, the two pieces could be unified to execute the wanted behavior:

productUser firstSystem
productUser secondSystem

推荐答案

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

签名

  createProduct :: Product p => f -> p

并不是您显然想的那样:在Java中,这表示我将生成 Product 的子类的某个对象,但不要问是哪个."在Haskell–中其子类是 not 子类型!–这意味着,对于您请求的 Product 任何(特定!)实例,我将为您提供该具体类型的对象."这是不可能的,因为 FirstFactory 显然不能提供 SecondProduct .

means not what you apparently think: in Java, this would say "I'll generate some object of a subclass of Product, don't ask which though." In Haskell – whose subclasses are not subtypes! – this means, "for any (specific!) instance of Product that you request, I'll give you an object of that concrete type." Which isn't possible, since FirstFactory apparently can't supply a SecondProduct.

要表达您想说的话,您需要一个显式包装器来包含任何子类".我们称其为 existential ,它的写法是:

To express what you tried to say, you need an explicit wrapper to contain "any subclass". We call that an existential, it's written thus:

{-# LANGUAGE GADTs       #-}

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

有了这个,你可以写

class Factory f where
  createProduct :: f -> AnyProduct

对于某些 yourProduct :: AnyProduct ,您可以通过以下方式调用 toString 方法":

For some yourProduct :: AnyProduct, you can then "call the toString method" this way:

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

但是由于实际上这是唯一的事情,因此您可以使用 AnyProduct 值进行操作(就像在OO语言中一样,您无法访问未知子类的字段/方法),整个 AnyProduct 类型实际上完全等同于 String !同样, AnyFactory 也将等同于此.因此,基本上,您发布的整个代码等同于

But since this is actually the only thing you can do with an AnyProduct value (like in OO language, you can't access fields/methods of the unknown subclass), the whole AnyProduct type is actually completely equivalent to String alone! And by the same argument, AnyFactory would again be equivalent to that. So basically, the entire code you posted is equivalent to

type Product = String

...

存在的内容通常皱着眉头,您只应在特殊情况下使用它们,而不应仅用于OO语言通过子类实现.

Existentials are quite generally frowned upon, you should only use them in special cases, not for anything just because OO languages do it with subclassing.

这篇关于哈斯克尔的抽象工厂的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆