在项目中我有几种不同的类型,在不同的模块中定义,每个模块都有相关的功能(这些功能具有相同的名称和非常相似的含义,所以下面的内容很有意义)。现在我想创建一个列表,在该列表中可以同时拥有所有这些类型的实例。我能想到的唯一可能是这样的事情:
data Common = A{...} | B{...} | ...
但它意味着将定义保留在一个地方,而不是在不同的模块中(对于A,B,......)。有更好的方法吗?
UPD
我对haskell很新,并写了一些与我的学习有关的课程。在这种情况下,我有不同的FormalLanguage
定义方法:FiniteAutomata
,Grammars
等等。它们中的每一个都具有共同的功能(isAccepted
,representation
,...),所以有一个列表,其中元素可以是任何这些类型,这似乎是合乎逻辑的。
答案 0 :(得分:6)
通过假设正确的解决方案是在列表中存储不同的类型,您将为Oaskell带来OOP思维模式。我将从检查这个假设开始。
通常我们将不同类型存储在同类列表中,因为它们支持通用接口。为什么不将公共接口分解出来并将其存储在列表中?
不幸的是,你的问题没有描述那个通用接口是什么,因此我将仅举几个常见的例子作为演示。
第一个示例是一堆值x
,y
和z
,它们都支持Show
函数,该函数具有签名:
(Show a) => a -> String
我们可以直接在值上调用show
而不是存储我们希望稍后显示的类型,而是将结果字符串存储在列表中:
list = [show x, show y, show z] :: String
过早调用show
没有任何代价,因为Haskell是一种惰性语言,在实际需要字符串之前实际上不会评估show
。
或者类型可能支持多种方法,例如:
class Contrived m where
f1 :: m -> String -> Int
f2 :: m -> Double
我们可以将上述表单的类转换为等效的字典,其中包含将方法部分应用于我们的值的结果:
data ContrivedDict = ContrivedDict {
f1' :: String -> Int,
f2' :: Double }
...我们可以使用这个字典将任何值打包到我们期望它支持的公共接口中:
buildDict :: (Contrived m) => m -> ContrivedDict
buildDict m = ContrivedDict { f1' = f1 m, f2' = f2 m }
然后我们可以将这个公共接口本身存储在列表中:
list :: [buildDict x, buildDict y, buildDict z]
同样,我们没有存储明确类型的值,而是将它们的常用元素排除在列表中。
但是,这个技巧并不总是有效。病理示例是期望两个相同类型的操作数的任何二元运算符,例如来自(+)
类的Num
运算符,其具有以下类型:
(Num a) => a -> a -> a
据我所知,没有一个好的基于字典的解决方案,可以部分应用二进制操作并以这样的方式存储它,以确保它应用于同一类型的第二个操作数。在这种情况下,存在类型类可能是唯一有效的方法。但是,我建议你尽可能坚持基于字典的方法,因为它允许比基于类型的方法更强大的技巧和转换。
有关此技术的更多信息,我建议您阅读Luke Palmer的文章:Haskell Antipattern: Existential Typeclass。
答案 1 :(得分:5)
几乎没有可能:
可能性1:
data Common = A AT | B BT | C CT
AT,BT和CT在各自的模块中描述
可能性2:
{-# LANGUAGE ExistentialQuantification #-}
class CommonClass a where
f1 :: a -> Int
data Common = forall a . CommonClass a => Common a
这与OOP超类几乎相同,但你不能做“向下倾斜”。然后,您可以在所有模块中声明公共类成员的实现。
@Gabriel Gonzalez建议的可能性3:
data Common = Common {
f1 :: Int
}
因此,您的模块通过使用闭包来抽象“私有”部分来实现通用接口。
然而,Haskell设计通常与OOP设计完全不同。虽然可以在Haskell中实现每个OOP技巧,但它可能是非惯用的,因此@dflemstr表示欢迎提供有关您的问题的更多信息。