使用代数数据类型或多态性,是否有一个Haskell相当于OOP的抽象类?

ult*_*fez 32 polymorphism haskell functional-programming 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
Run Code Online (Sandbox Code Playgroud)

在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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

luq*_*qui 64

是的,你是对的,你正在寻找代数数据类型.在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");
}
Run Code Online (Sandbox Code Playgroud)

翻译成:

data IntBox = Empty | Full Int

get :: IntBox -> Int
get Empty = 0
get (Full x) = x
Run Code Online (Sandbox Code Playgroud)

功能记录

这种风格不允许向下转换,因此上述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"); }
}
Run Code Online (Sandbox Code Playgroud)

它在Haskell中的翻译不会将类型映射到类型中. Animal是唯一的类型,Dog以及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" }
Run Code Online (Sandbox Code Playgroud)

这个基本概念有几种不同的排列.不变量是抽象类型是记录类型,其中方法是记录的字段.

编辑:关于这种方法的一些细微之处的评论中有一个很好的讨论,包括上面代码中的一个错误.

类型类

这是我最不喜欢的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"
Run Code Online (Sandbox Code Playgroud)

这看起来不错,不是吗?当你意识到即使它看起来像OO时,它也不会像OO那样真正起作用.您可能想要一份动物清单,但您现在可以做的最好的是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
Run Code Online (Sandbox Code Playgroud)

然后[AnyAnimal]就是你想要的动物名单.然而,事实证明,AnyAnimal暴露出与第二个例子中的记录完全相同的自身信息Animal,我们刚刚以迂回的方式解决了这个问题.因此,为什么我不认为类型类是一个非常好的OO编码.

从而结束本周的Way Too Much信息版本!

  • 您的函数记录示例包含一个错误,可以方便地说明为什么OO的这种编码也有些尴尬.如果我从那个例子中运行`speak cat`,我会得到一个例外:`记录构造中缺少字段Main.catchPhrase`.OO语言和类型类为您提供递归结,但必须手动完成记录中保存的"方法".所以我们应该使用`putStrLn来说'speak :: Animal - > IO()`.catchPhrase`作为其原型定义,并且"说猫猫"作为不幸的调用语法. (7认同)
  • @myself:对`( - >)t`使用`Monad`实例(在`Control.Monad.Instances`中可用),`cat`和`dog`构造函数也可以看起来更传统: `dog = do self < - protoAnimal; return self {catchPhrase ="Woof",speak = putStrLn"Rowwrlrw"}` (4认同)
  • @dhaffey,哦!很好的观察,现在很明显,我看到了它.我想非类型类的事情是使用开放式修复点模式.所以`animalClass :: Animal - > Animal`,然后定义开始`dog this =`,我们有`instantiate = fix`. (2认同)