如何在Haskell中实现这个OOP案例?

apl*_*vin 1 oop haskell types

在项目中我有几种不同的类型,在不同的模块中定义,每个模块都有相关的功能(这些功能具有相同的名称和非常相似的含义,所以下面的内容有意义).现在我想创建一个列表,在该列表中可以同时拥有所有这些类型的实例.我能想到的唯一可能是这样的事情:

data Common = A{...} | B{...} | ...
Run Code Online (Sandbox Code Playgroud)

但它意味着将定义保持在一个地方,而不是在不同的模块中(对于A,B,......).有一个更好的方法吗?

UPD

我对haskell很新,并写了一些与我的学习有关的课程.在这种情况下,我有不同的FormalLanguage定义方法:FiniteAutomata,Grammars等等.它们中的每一个都具有共同的功能(isAccepted,representation...),因此有一个列表,其中元素可以是任何这些类型似乎是合乎逻辑的.

Gab*_*lez 6

您假设正确的解决方案是在列表中存储不同的类型,从而为Haskell带来OOP思维模式.我将从检查这个假设开始.

通常我们将不同类型存储在同类列表中,因为它们支持通用接口.为什么不将公共接口分解出来并将其存储在列表中?

不幸的是,你的问题没有描述那个通用接口是什么,所以我将简单介绍一些常见的例子作为演示.

第一个例子是一堆数值,x,y,并且z,所有的支持Show功能,其中有签名:

(Show a) => a -> String
Run Code Online (Sandbox Code Playgroud)

我们可以show直接调用值并将结果字符串存储在列表中,而不是存储我们希望稍后显示的类型:

list = [show x, show y, show z] :: String
Run Code Online (Sandbox Code Playgroud)

show因为Haskell是一种懒惰语言并且在show我们实际需要字符串之前不会实际评估s,所以不会因为过早调用而受到惩罚.

或者类型可能支持多种方法,例如:

class Contrived m where
    f1 :: m -> String -> Int
    f2 :: m -> Double
Run Code Online (Sandbox Code Playgroud)

我们可以将上面形式的类转换为等效的字典,其中包含将方法部分应用于我们的值的结果:

data ContrivedDict = ContrivedDict {
    f1' :: String -> Int,
    f2' :: Double }
Run Code Online (Sandbox Code Playgroud)

...我们可以使用这个字典将任何值打包到我们期望它支持的公共接口中:

buildDict :: (Contrived m) => m -> ContrivedDict
buildDict m = ContrivedDict { f1' = f1 m, f2' = f2 m }
Run Code Online (Sandbox Code Playgroud)

然后我们可以将这个公共接口本身存储在列表中:

list :: [buildDict x, buildDict y, buildDict z]
Run Code Online (Sandbox Code Playgroud)

同样,我们没有存储明确类型的值,而是将它们的常用元素排除在列表中.

但是,这个技巧并不总是有效.病理示例是任何二元运算符,它期望两个相等类型的操作数,例如类中的(+)运算符Num,它具有以下类型:

(Num a) => a -> a -> a
Run Code Online (Sandbox Code Playgroud)

据我所知,没有一个好的基于字典的解决方案,可以部分应用二进制操作并以这样的方式存储它,以确保它应用于同一类型的第二个操作数.在这种情况下,存在类型类可能是唯一有效的方法.但是,我建议你尽可能坚持基于字典的方法,因为它允许比基于类型的方法更强大的技巧和转换.

有关此技术的更多信息,我建议您阅读Luke Palmer的文章:Haskell Antipattern:Existential Typeclass.

  • 据我所知,对于像实际OO语言中的二进制操作一样,没有好的OO解决方案.根据我的经验,大多数人都有特殊的烘焙支持,依赖C风格的重载功能,或使用神秘的元编程技巧. (4认同)

npo*_*cop 5

可能性很小:

可能性1:

data Common = A AT | B BT | C CT
Run Code Online (Sandbox Code Playgroud)

在各自的模块中描述了AT,BT和CT

可能性2:

{-# LANGUAGE ExistentialQuantification #-}

class CommonClass a where
    f1 :: a -> Int

data Common = forall a . CommonClass a => Common a
Run Code Online (Sandbox Code Playgroud)

这与OOP超类几乎相同,但你不能做"垂头丧气".然后,您可以在所有模块中声明公共类成员的实现.

@Gabriel Gonzalez建议的可能性3:

data Common = Common {
     f1 :: Int
}
Run Code Online (Sandbox Code Playgroud)

因此,您的模块通过使用闭包来抽象"私有"部分来实现通用接口.

但是,Haskell设计通常与OOP设计完全不同.虽然可以在Haskell中实现每个OOP技巧,但它可能是非惯用的,因此@dflemstr表示欢迎提供有关您的问题的更多信息.

  • [强制性](https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/) (2认同)