是的,你是对的,你正在寻找代数数据类型。有一个关于它们的很棒的教程:向你学习 Haskell http://learnyouahaskell.com/making-our-own-types-and-typeclasses#algebraic-data-types.
根据记录,OOP 中的抽象类概念实际上在 Haskell 中有三种不同的翻译,ADT 只是其中之一。以下是这些技术的快速概述。
代数数据类型
代数数据类型对抽象类的模式进行编码,该抽象类的子类是已知的,并且其中函数通过向下转换来检查该对象是哪个特定实例的成员。
abstract class IntBox { }
class Empty : IntBox { }
class Full : IntBox {
int inside;
Full(int inside) { this.inside = inside; }
}
int Get(IntBox a) {
if (a is Empty) { return 0; }
if (a is Full) { return ((Full)a).inside; }
error("IntBox not of expected type");
}
翻译成:
data IntBox = Empty | Full Int
get :: IntBox -> Int
get Empty = 0
get (Full x) = x
功能记录
这种风格不允许向下投射,所以Get
上面的函数无法用这种风格来表达。所以这是完全不同的东西。
abstract class Animal {
abstract string CatchPhrase();
virtual void Speak() { print(CatchPhrase()); }
}
class Cat : Animal {
override string CatchPhrase() { return "Meow"; }
}
class Dog : Animal {
override string CatchPhrase() { return "Woof"; }
override void Speak() { print("Rowwrlrw"); }
}
它在 Haskell 中的翻译不会将类型映射到类型。Animal
是唯一的类型,并且Dog
and Cat
被压缩到它们的构造函数中:
data Animal = Animal {
catchPhrase :: String,
speak :: IO ()
}
protoAnimal :: Animal
protoAnimal = Animal {
speak = putStrLn (catchPhrase protoAnimal)
}
cat :: Animal
cat = protoAnimal { catchPhrase = "Meow" }
dog :: Animal
dog = protoAnimal { catchPhrase = "Woof", speak = putStrLn "Rowwrlrw" }
这个基本概念有几种不同的排列。不变的是抽象类型是记录类型,其中方法是记录的字段。
编辑:评论中对此方法的一些微妙之处进行了很好的讨论,包括上述代码中的错误。
类型类
这是我最不喜欢的 OO 思想编码。它对于 OO 程序员来说很舒服,因为它使用熟悉的单词并将类型映射到类型。但是当事情变得复杂时,上面的函数记录方法往往更容易使用。
我将再次对 Animal 示例进行编码:
class Animal a where
catchPhrase :: a -> String
speak :: a -> IO ()
speak a = putStrLn (catchPhrase a)
data Cat = Cat
instance Animal Cat where
catchPhrase Cat = "Meow"
data Dog = Dog
instance Animal Dog where
catchPhrase Dog = "Woof"
speak Dog = putStrLn "Rowwrlrw"
这看起来不错,不是吗?当您意识到尽管它看起来像面向对象,但它实际上并不像面向对象那样工作时,困难就来了。您可能想要一份动物列表,但您现在能做的最好的事情是Animal a => [a]
,同质动物的列表,例如。仅包含猫或仅包含狗的列表。然后你需要制作这个包装类型:
{-# LANGUAGE ExistentialQuantification #-}
data AnyAnimal = forall a. Animal a => AnyAnimal a
instance Animal AnyAnimal where
catchPhrase (AnyAnimal a) = catchPhrase a
speak (AnyAnimal a) = speak a
进而[AnyAnimal]
是您想要的动物列表。然而事实证明AnyAnimal
暴露exactly与自身相同的信息Animal
在第二个例子中记录,我们只是以一种迂回的方式进行了处理。因此,为什么我不认为类型类是一种非常好的 OO 编码。
本周的版本就这样结束了信息太多了!