haskell - 是否存在使用代数数据类型或多态性的 OOP 抽象类的 Haskell 等效项?

标签 haskell functional-programming polymorphism typeclass algebraic-data-types

在 Haskell 中,是否可以编写一个具有签名的函数,该函数可以接受两种不同(尽管相似)的数据类型,并根据传入的类型进行不同的操作?

举个例子可能会让我的问题更清楚。如果我有一个名为 myFunction 的函数,以及两个名为 MyTypeAMyTypeB 的类型,我可以定义 myFunction 以便它只能接受 MyTypeAMyTypeB 类型的数据作为其第一个参数?

type MyTypeA = (Int, Int, Char, Char)
type MyTypeB = ([Int], [Char])

myFunction :: MyTypeA_or_MyTypeB -> Char
myFunction constrainedToTypeA = something
myFunction constrainedToTypeB = somethingElse

在 OOP 语言中,您可以像这样编写我想要实现的目标:

public abstract class ConstrainedType {
}

public class MyTypeA extends ConstrainedType {
    ...various members...
}

public class MyTypeB extends ConstrainedType {
    ...various members...
}

...

public Char myFunction(ConstrainedType a) {
    if (a TypeOf MyTypeA) {
        return doStuffA();
    }
    else if (a TypeOf MyTypeB) {
        return doStuffB();
    }
}

我一直在阅读有关代数数据类型的内容,我认为我需要定义一个 Haskell 类型,但我不确定如何定义它以便它可以存储一种类型或另一个,以及我如何在自己的函数中使用它。

最佳答案

是的,你是对的,你正在寻找代数数据类型。在 Learn You a Haskell 有一个关于它们的很棒的教程。 .

郑重声明,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 是唯一的类型,DogCat 被压缩到它们的构造函数中:

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 公开了与第二个示例中的 Animal 记录完全相同相同的信息,我们刚刚讨论过以一种迂回的方式。因此,为什么我不认为类型类是一种很好的 OO 编码。

本周的《信息太多了!》就此结束!

关于haskell - 是否存在使用代数数据类型或多态性的 OOP 抽象类的 Haskell 等效项?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4029455/

相关文章:

haskell - Haskell 中科学记数法的阅读更方便

javascript - 如何从头开始用 Javascript 编写异步 Map 函数?

Java 2D 游戏多态性

haskell - Cabal 可以找到给定软件包集的匹配版本吗?

haskell - 有 "ApplicativeIO"类吗?

haskell - 用管道的 WriterP 编写一个简单的累加器

java - Java 中的柯里化(Currying)函数

javascript - 过滤数组并获取第一个值作为唯一 id 的对象数组

c++ - 带有 shared_ptr 的虚拟构造函数

java - 在java中转换为父类型时调用子方法