使用代数数据类型或多态是否存在与OOP的抽象类相当的Haskell? [英] Is there a Haskell equivalent of OOP's abstract classes, using algebraic data types or polymorphism?

查看:106
本文介绍了使用代数数据类型或多态是否存在与OOP的抽象类相当的Haskell?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



一个例子可能会让我的问题更清晰。如果我有一个名为 myFunction 的函数,以及两个名为 MyTypeA MyTypeB ,我可以定义 myFunction ,以便它只接受类型为 MyTypeA 或<$ c $的数据c> MyTypeB 作为第一个参数?

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

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

在OOP语言,你可以编写我想要实现的内容:

  public abstract class ConstrainedType {
}

public class MyTypeA extends ConstrainedType {
...各种成员...
}

公共类MyTypeB扩展ConstrainedType {
...各种成员......
}

...

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

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

解决方案

是的,您是正确的,您正在寻找代数数据类型。在学习你是一个Haskell <为了记录,从OOP抽象类的概念实际上有三种不同的翻译成Haskell,并且ADT仅仅是一种。下面是对这些技术的简要概述。

代数数据类型



代数数据类型对抽象类的子类已知,函数通过向下转换来检查对象是哪个特定实例。

  abstract类IntBox {} 

类空:IntBox {}

类完整:IntBox {
int inside;
Full(int inside){this.inside = inside; }
}

int Get(IntBox a){
if(a为空){return 0; }
if(a是Full){return((Full)a).inside; }
错误(IntBox不是预期的类型);

$ / code $ / $ p

转换为:

 数据IntBox =空| 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(){returnMeow; }
}

class Dog:Animal {
覆盖字符串CatchPhrase(){returnWoof; }
override void Speak(){print(Rowwrlrw);它在Haskell中的翻译不会将类型映射到类型中。 Animal 是唯一的类型,并且 Dog Cat 是压扁了它们的构造函数:

  data动物=动物{
catchPhrase :: String,
说:: IO()
}

protoAnimal :: Animal
protoAnimal = Animal {
speak = putStrLn(catchPhrase protoAnimal)
}

cat :: Animal
cat = protoAnimal {catchPhrase =Meow}

dog :: Animal
dog = protoAnimal {catchPhrase =Woof,speak = putStrLnRowwrlrw}

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



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

类型类



这是我最不喜欢的编码OO的想法。 OO程序员很舒服,因为它使用熟悉的单词和地图类型来表示类型。但是上面的函数记录在事情变得复杂的时候更容易处理。



我会再次对Animal示例进行编码:

  class Animal a where 
catchPhrase :: a - > String
speak :: a - > IO()

speak a = putStrLn(catchPhrase a)

数据Cat = Cat
实例Animal Cat其中
catchPhrase Cat =Meow

数据狗=狗
实例动物狗其中
catchPhrase狗=Woof
说Dog = putStrLnRowwrlrw

这看起来不错,不是吗?当你意识到即使它看起来像面向对象的时候,困难就出现了,它并不像面向对象那样工作。你可能想要一个动物列表,但你现在可以做的最好的是 Animal a => [a] ,同类动物列表,例如。只有猫或只有狗的列表。然后你需要做这个包装类型:

  data AnyAnimal = forall a。动物a => AnyAnimal a 
实例Animal AnyAnimal其中
catchPhrase(AnyAnimal a)= catchPhrase a
speak(AnyAnimal a)=说出

然后 [AnyAnimal] 是您想要的动物列表。然而,事实证明 AnyAnimal 完全暴露了 在第二个例子中记录,我们刚刚以一种迂回的方式走过去。因此,为什么我不认为类型类是一个非常好的面向对象编码。



因此,本周结束了方式太多信息!


In Haskell, is it possible to write a function with a signature that can accept two different (although similar) data types, and operate differently depending on what type is passed in?

An example might make my question clearer. If I have a function named myFunction, and two types named MyTypeA and MyTypeB, can I define myFunction so that it can only accept data of type MyTypeA or MyTypeB as its first parameter?

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

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

In an OOP language, you could write what I'm trying to achieve like so:

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();
    }
}

I've been reading about algebraic data types and I think I need to define a Haskell type, but I'm not sure how to go about defining it so that it can store one type or another, and also how I use it in my own functions.

解决方案

Yes, you are correct, you are looking for algebraic data types. There is a great tutorial on them at Learn You a Haskell.

For the record, the concept of an abstract class from OOP actually has three different translations into Haskell, and ADTs are just one. Here is a quick overview of the techniques.

Algebraic Data Types

Algebraic data types encode the pattern of an abstract class whose subclasses are known, and where functions check which particular instance the object is a member of by down-casting.

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");
}

Translates into:

data IntBox = Empty | Full Int

get :: IntBox -> Int
get Empty = 0
get (Full x) = x

Record of functions

This style does not allow down-casting, so the Get function above would not be expressible in this style. So here is something completely different.

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"); }
}

Its translation in Haskell doesn't map types into types. Animal is the only type, and Dog and Cat are squashed away into their constructor functions:

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" }

There are a few different permutations of this basic concept. The invariant is that the abstract type is a record type where the methods are the fields of the record.

EDIT: There is a good discussion in the comments on some of the subtleties of this approach, including a bug in the above code.

Typeclasses

This is my least favorite encoding of OO ideas. It is comfortable to OO programmers because it uses familiar words and maps types to types. But the record of functions approach above tends to be easier to work with when things get complicated.

I'll encode the Animal example again:

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"

This looks nice, doesn't it? The difficulty comes when you realize that even though it looks like OO, it doesn't really work like OO. You might want to have a list of Animals, but the best you can do right now is Animal a => [a], a list of homogeneous animals, eg. a list of only Cats or only Dogs. Then you need to make this wrapper type:

data AnyAnimal = forall a. Animal a => AnyAnimal a
instance Animal AnyAnimal where
    catchPhrase (AnyAnimal a) = catchPhrase a
    speak (AnyAnimal a) = speak a

And then [AnyAnimal] is what you want for your list of animals. However, it turns out that AnyAnimal exposes exactly the same information about itself as the Animal record in the second example, we've just gone about it in a roundabout way. Thus why I don't consider typeclasses to be a very good encoding of OO.

And thus concludes this week's edition of Way Too Much Information!

这篇关于使用代数数据类型或多态是否存在与OOP的抽象类相当的Haskell?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆