将包含所有操作的自定义 haskell 类型封装到一个 class

Encapsulating a custom haskell type with all operations into one class

如果我想在 C++ 中创建一个新类型,在其中重载一堆运算符,我可以这样做:

class Stringy {
    public:
    explict Stringy(const char *buffer){}
    friend Stringy operator + (const Stringy &s1, const Stringy &s2) { ... }
    friend Stringy operator - (const Stringy &s1, const Stringy &s2) { ... }
    friend std::ostream& operator << (std::ostream &oss, const String& s1) { ... }
};

现在如果我尝试在 Haskell 中做同样的事情,我发现自己在做(一个例子):

data Stringy = Stringy([Char])
-- do something unrelated here --
(+) :: Stringy -> Stringy -> Stringy
(-) :: Stringy -> Stringy -> Stringy
(removeDups) :: Stringy -> Stringy
-- do something else unrelated here --
(>>) :: Stringy -> IO --(is IO right?)

我的观点是,c++ 看起来更像是组合在一起的,因为你在 class 中所做的一切都以某种方式与 class 相关。另一方面,haskell 的运算符声明可能到处都是,不需要在一起。如果有人看一下 c++ 代码,他们可以立即识别哪些操作是 class 的一部分,哪些不是。我如何在 Haskell 中实现同样的一致性?

我还是Haskell的初学者,所以请用简单的方式做事。谢谢

正如 Daniel 所说,优秀的库设计者确实会尝试将相关功能放入同一个模块中。因此,通常类型类 StringyStringy 上的所有 "basic" 操作都将在模块 Stringy(或 X.Y.Z.Stringy)中,在文件 Stringy.hs.

我想您最终会欣赏 Haskell 给您带来的灵活性。 例如,假设您要定义类型为

的函数
myfunction :: Stringy -> Thingy -> Doohicky

这实际上不适合三个类型类(StringyThingyDoohicky)中的任何一个,因为它引用了不属于类型类的类型. 也许这三个类型类位于三个不同的模块中,很久以前由不同的人编写。 你是第一个意识到 myFunction 是一个有用的操作的人。 Haskell 允许您将此函数放在您认为最有意义的位置。 您可能会创建一个全新的模块,专门用于从其他类型类的值组合中生成 Doohickys 的方法。