Haskell中的面向对象编程风格继承

5
在C#中,我可以声明以下内容:
class A {
    int Field;
}

class B : A {
    int Field2;
}

static int f(A a) { return a.Field; }
static int f(B b) { return a.Field + b.Field2; }

static void Main(string[] args) {
    A a = new A() { Field = 1 };
    A b = new B() { Field = 1, Field = 2};

    Console.WriteLine(f(a) + f(b));
}

在Haskell中,我会将上面的内容键入为:
data A = A { field :: Int } | B { field :: Int, field2 :: Int }

f :: A -> Int
f (A a) = a
f (B a b) = a + b

main :: IO()
main = do putStrLn $ show (f(a) + f(b))
    where a = A 1
          b = B 1 2

我不喜欢Haskell中的这一点,即我必须在A的数据定义中重复两次使用field(随着存在于A中需要出现在B中的字段数量的增加,这变得更加繁琐)。在Haskell中是否有更简洁的方法将B写为A的子类(与C#的方式有些类似)?

2个回答

6

如果A有很多字段,一个合理的设计是为A和B设置两种不同的数据类型,其中B包含A,然后使用typeclass来定义f。就像这样:

data A = A {field1 :: Int, field2 :: Int, ..., field9999 :: Int}
data B = B {a :: A, field10000 :: Int}

class ABC a where
    f :: a -> Int

instance ABC A where
    f a = field1 a + field101 a

instance ABC B where
    f b = f (a b) + field10000 b

1
是的,我认为这是正确的方法。请注意,C#代码中的f是一个重载函数。与继承完全无关,重载函数将始终需要定义为类型类。实际上,Haskell的类型类与函数重载比与OOP“类”更相似。 - C. A. McCann
@seppk- 目前我的代码采用这种组合方式。我原本希望能够避免使用它,但我认为在这段时间内它是必须的。感谢您的答案。@camccann,这些函数只是为了说明实例的操作,并非本文的重点是重载方面,尽管如此感谢您对两种语言之间关系的洞察力。 - GEL

3

你的数据定义是一个横向移动,因为A和B构造函数都是类型A的实例,而你要找的是一个类型B,它是一个类型A,但不仅仅是类型A的实例。

尝试使用:

data Foo = Foo {field :: Type}
newtype Bar = Bar Foo

fromBar Bar x = x
toBar x = Bar x

这将允许您在类型Bar上声明各种特殊函数,这些函数不适用于Foo,同时使用为类型Foo设计的函数操作Bar。
如果您想在Bar中使用数据参数扩展额外信息到Foo中,可以使用数据声明和具有关系。这不如使用newtype理想,因为newtype是惰性评估的,而数据声明是严格的。但仍然可以这样做:
data Foo = Foo {field :: Type}
data Bar = Bar {fooField :: Foo, moreField :: Type}

正如sepp2k所指出的那样,定义类型的“本质”不是通过外观,而是通过其“功能”。在Haskell中,我们通过定义一个类型类,并有一个类型实例化该类来实现这一点。
希望这可以帮助你。

1
感谢提供另一种处理此问题的观点。我现在明白了两种编程语言在实现目标时的思维模式差异。 - GEL
没问题,解释事情可以帮助我更好地理解它们。 - Dan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接