ult*_*fez 32 polymorphism haskell functional-programming typeclass algebraic-data-types
在Haskell中,是否可以编写一个带有签名的函数,该签名可以接受两种不同(尽管相似)的数据类型,并根据传入的类型进行不同的操作?
一个例子可能会让我的问题更加清晰.如果我有一个函数命名myFunction
,和两个类型的命名MyTypeA
和MyTypeB
,我可以定义myFunction
,使其只能接受类型的数据MyTypeA
或MyTypeB
作为其第一个参数?
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信息版本!