Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/silverlight/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何将我在OOP中的想法转换为Haskell?_Haskell - Fatal编程技术网

如何将我在OOP中的想法转换为Haskell?

如何将我在OOP中的想法转换为Haskell?,haskell,Haskell,例如,我有一个容器类型来保存具有公共字符的元素。我还提供了一些类型作为元素。我还希望这个函数可以很容易地扩展(其他人可以创建自己的元素类型并由我的容器保存) 因此,我: class ElementClass data E1 = E1 String instance ElementClass E1 data E2 = E2 Int instance ElementClass E2 data Element = forall e. (ElementClass e) => Element e d

例如,我有一个容器类型来保存具有公共字符的元素。我还提供了一些类型作为元素。我还希望这个函数可以很容易地扩展(其他人可以创建自己的元素类型并由我的容器保存)

因此,我:

class ElementClass
data E1 = E1 String
instance ElementClass E1
data E2 = E2 Int
instance ElementClass E2
data Element = forall e. (ElementClass e) => Element e
data Container = Container [Element]
这很好,直到我需要单独处理元素。由于forall,函数“f::Element->IO()”无法知道它到底是什么元素


在Haskell风格中,正确的方法是什么?

好的,我会尽力帮助你

第一:我假设您有以下数据类型:

data E1 = E1 String
data E2 = E2 Int
你对这两者都有一个合理的操作,我将调用
say

say1 :: E1 -> String -> String
say1 (E1 s) msg = msg ++ s

say2 :: E2 -> String -> String
say2 (E2 i) msg = msg ++ show i
因此,没有任何类型类或东西,您可以做的是:

type Messanger = String -> String
不要使用批次为
E1
E2
的容器,而是使用批次为
Messagner
s的容器:

sayHello :: [Messanger] -> String
sayHello = map ($ "Hello, ")

sayHello [say1 (E1 "World"), say2 (E2 42)]
> ["Hello, World","Hello, 42"]
我希望这能帮你一点忙-事情就是离开对象,转而看操作

因此,与其将对象/数据推送到一个应该处理对象数据和行为的函数,不如使用一个通用的“接口”来完成您的工作

如果你给我一些更好的类和方法的例子(例如,两种可能确实共享某些特性或行为的类型-
String
Int
在这方面确实缺乏),我将更新我的答案

知道它到底是什么元素

要知道这一点,您当然应该使用一个简单的ADT

data Element' = E1Element E1
              | E2Element E2
              | ...
这样,您就可以在容器中的哪一个上进行模式匹配

现在,这与

其他人可以创建自己的元素类型并由我的容器保存

而且一定会发生冲突!当其他人被允许向元素列表添加新类型时,就无法安全地匹配所有可能的情况。所以,如果你想匹配,唯一正确的方法就是拥有一组封闭的可能性,就像ADT给你的那样

OTOH,像您最初想到的一样,允许类型的类是开放的。没关系,但这只是因为实际上无法访问确切的类型,而只能访问由
forall e定义的公共接口。元素e类


在哈斯凯尔,存在主义者确实有点不受欢迎,因为他们太过于保守了。但有时这是非常正确的做法,您的应用程序可能是一个很好的例子。

首先,确保您阅读并理解。您的示例代码比需要的更复杂

基本上,您要问的是如何在Haskell中将值从超类型转换为子类型中执行等效的向下转换。这种操作本质上可能失败,因此类型类似于
元素->可能是E1

这里要问的第一个问题是:你真的需要吗?有两个互补的替代方案。首先:你可以用这样一种方式来表达你的“超类型”,即它只有有限的、固定数量的“子类型”。然后你实现你的类型就像一个并集:

data Element = E1 String | E2 Int
每次您想要使用
元素
模式匹配和预处理时,您都有特定于案例的数据:

processElement :: Element -> whatever
processElement (E1 str) = ...
processElement (E2 i) = ...
这种方法的缺点是:

  • 联合类型只能有一组固定的子类别
  • 每次添加子类别时,都必须修改所有现有操作,以便为其添加额外的匹配类别
  • 正面是:

  • 通过枚举类型中的所有子类,您可以使用编译器告诉您何时遗漏了一个子类
  • 添加新操作很容易,并且不需要修改任何现有代码
  • 第二种方法是将类型重新格式化为“接口”。我的意思是,您的类型现在将被建模为记录类型,其每个字段构成一个“方法”:

    这样做的好处是,您现在可以拥有任意多的子类别,并且可以轻松地添加它们,而无需修改现有操作。它有以下两个缺点:

  • 如果需要添加新操作,则必须向
    元素
    类型添加“方法”(字段),并修改构成
    元素
    的每个现有函数
  • 元素
    类型的使用者永远无法分辨他们正在处理的子类别,也无法获取特定于此子类别的信息。例如,消费者无法分辨特定的
    元素
    是用
    makeE2
    构建的,更不用说提取这样一个
    元素
    封装的
    Int
  • (请注意,您的存在主义示例相当于这种“接口”方法,并且具有相同的优点和局限性。这只是不必要的冗长。)

    但如果你真的坚持要有相当于沮丧的情绪,还有第三种选择:使用模块。
    Dynamic
    值是一个不可变的容器,它包含一个实例化
    Typeable
    类(GHC可以为您派生)的任何类型的值。例如:

    data E1 = E1 String deriving Typeable
    data E2 = E2 Int deriving Typeable
    
    newtype Element = Element Dynamic
    
    makeE1 :: String -> Element
    makeE1 str = Element (toDyn (E1 str))
    
    makeE2 :: Int -> Element
    makeE2 i = Element (toDyn (E2 i))
    
    -- Cast an Element to E1
    toE1 :: Element -> Maybe E1
    toE1 (Element dyn) = fromDynamic dyn
    
    -- Cast an Element to E2
    toE2 :: Element -> Maybe E2
    toE2 (Element dyn) = fromDynamic dyn
    
    -- Cast an Element to whichever type the context expects
    fromElement :: Typeable a => Element -> Maybe a
    fromElement (Element dyn) = fromDynamic dyn
    
    这是最接近OOP向下投射操作的解决方案。这样做的缺点是,降级本质上不是类型安全的。让我们回到几个月后需要在代码中添加
    E3
    子类的情况。现在的问题是,代码中有很多函数,它们测试
    元素是
    E1
    还是
    E2
    ,它们是在
    E3
    之前编写的。添加第三个子类时,这些函数中有多少会中断?祝你好运,因为编译器无法帮助你

    请注意,我描述的这三种备选方案也存在于OOP中,包括以下三种备选方案:

  • union类型的OOP对应物是Visitor模式,这意味着可以轻松地向类型添加新操作,而无需修改其子类。(嗯,相对容易。访客模式是hella-ver
    data E1 = E1 String deriving Typeable
    data E2 = E2 Int deriving Typeable
    
    newtype Element = Element Dynamic
    
    makeE1 :: String -> Element
    makeE1 str = Element (toDyn (E1 str))
    
    makeE2 :: Int -> Element
    makeE2 i = Element (toDyn (E2 i))
    
    -- Cast an Element to E1
    toE1 :: Element -> Maybe E1
    toE1 (Element dyn) = fromDynamic dyn
    
    -- Cast an Element to E2
    toE2 :: Element -> Maybe E2
    toE2 (Element dyn) = fromDynamic dyn
    
    -- Cast an Element to whichever type the context expects
    fromElement :: Typeable a => Element -> Maybe a
    fromElement (Element dyn) = fromDynamic dyn