如何在函数式语言中实现面向对象的多态性?
How do you implement Object-Oriented polymorphism in a functional language?
假设您在面向对象的应用程序中有这个:
module Talker
def talk(word)
puts word
end
end
module Swimmer
def swim(distance)
puts "swimming #{distance}"
end
end
class Organism
def initialize
rise
end
def rise
puts "hello world"
end
end
class Animal extends Organism
def think(something)
puts "think #{something}"
end
end
class Bird extends Animal
include Talker
end
class Fish extends Animal
include Swimmer
end
bird = new Bird
fish = new Fish
在此,您可以调用每个方法都是唯一的:
bird.talk("hello")
fish.swim(50)
但是你也可以调用相同的方法:
bird.think("fly")
fish.think("swim")
如果我有一个接受动物的函数,我可以调用 think 函数:
def experience(animal)
animal.think("one")
animal.think("two")
animal.think("one")
end
在伪函数式语言中,你基本上可以这样做:
function experience(animal) {
think(animal)
think(animal)
think(animal)
}
但不是真的,你必须检查类型:
function think(genericObject) {
if (genericObject is Animal) {
animalThink(genericObject)
} else if (genericObject is SomethingElse) {
somethingElseThink(genericObject)
}
}
那是因为,在实现你的“体验”功能的时候,你不只是想要体验动物,你还想要体验石头、树木等其他东西,只是它们的体验功能不同而已。
function experience(thing) {
move(thing)
move(thing)
move(thing)
}
function move(thing) {
case thing {
match Animal then animalMove(thing)
match Plant then plantMove(thing)
match Rock then rockMove(thing)
}
}
通过这种方式,您无法拥有完全可重用的函数,您的函数必须知道它将在某处接收的特定类型。
有什么方法可以避免这种情况并使其更像函数式语言中的 OO 多态性?
如果是这样,在高层次上,如果可以用函数式语言解决它,它是如何工作的?
- Achieving polymorphism in functional programming
- https://www.quora.com/How-is-polymorphism-used-in-functional-programming-languages
- https://wiki.haskell.org/OOP_vs_type_classes
函数式编程语言有多种实现多态性的方法。我将对比 Java(我最了解的 OOP 语言)和 Haskell(我最了解的函数式语言)。
方式一:“参数多态”
有了参数多态性,您根本不需要了解基础类型。例如,如果我有一个包含 T 类型元素的单链表,实际上我不需要知道任何有关 T 类型的信息就可以找到列表的长度。我会写类似
的东西
length :: forall a . [a] -> Integer
length [] = 0
length (x:xs) = 1 + length xs
in Haskell(显然我想在实践中使用更好的算法,但你明白了)。请注意,列表元素的类型是什么并不重要;获取长度的代码是相同的。第一行是“类型签名”。它说对于每个类型 a,length 将采用 a 的列表并输出一个整数。
这不能用于太多“严重的多态性”,但绝对是一个强有力的开始。它大致对应于 Java 的泛型。
方式二:类型class式多态
即使像检查相等性这样良性的事情实际上也需要多态性。不同的类型需要不同的代码来检查相等性,并且对于某些类型(通常是函数),由于停机问题,检查相等性实际上是不可能的。因此,我们使用“type classes”。
假设我定义了一个包含 2 个元素的新类型,Bob 和 Larry。在 Haskell 中,这看起来像
data VeggieTalesStars = Bob | Larry
我希望能够比较 VeggieTalesStars 类型的两个元素是否相等。为此,我需要实现一个 Eq 实例。
instance Eq VeggieTalesStars where
Bob == Bob = True
Larry == Larry = True
Bob == Larry = False
Larry == Bob = False
请注意函数 (==) 具有类型签名
(==) :: forall b . Eq b => b -> b -> Bool
这意味着对于每个类型 b,如果 b 有一个 Eq 实例,那么 (==) 可以接受两个类型 b 的参数和 return 一个 Bool。
你可能不难猜到不等于函数 (/=) 也有类型签名
(/=) :: forall b . Eq b => b -> b -> Bool
因为 (/=) 由
定义
x /= y = not (x == y)
当我们调用(/=)函数时,该函数会根据参数的类型部署正确版本的(==)函数。如果参数具有不同的类型,您将无法使用 (/=).
来比较它们
Typeclass-style 多态性允许您执行以下操作:
class Animal b where
think :: b -> String -> String
-- we provide the default implementation
think b string = "think " ++ string
data Fish = Fish
data Bird = Bird
instance Animal Fish where
instance Animal Bird where
Fish 和 Bird 都实现了“Animal”类型class,因此我们可以在两者上调用 think 函数。也就是说,
>>> think Bird "thought"
"think thought"
>>> think Fish "thought"
"think thought"
此用例大致对应于 Java 接口 - 类型可以根据需要实现任意数量的类型 class。但是类型 classes 比接口强大得多。
方式 3:函数
如果你的对象只有一个方法,它也可能只是一个函数。这是避免继承层次结构的一种非常常见的方法 - 处理函数而不是 1-method 基础的继承者 class.
因此可以定义
type Animal = String -> String
basicAnimal :: Animal
basicAnimal thought = "think " ++ thought
“动物”实际上只是一种获取一根弦并产生另一根弦的方式。这将对应于 Java 代码
class Animal {
public String think(String thought) {
return "think " + thought;
}
}
假设在Java中,我们决定按如下方式实现动物的子class:
class ThoughtfulPerson extends Animal {
private final String thought;
public ThoughtfulPerson(final String thought) {
this.thought = thought;
}
@Override
public String think(String thought) {
System.out.println("I normally think " + this.thought ", but I'm currently thinking" + thought + ".");
}
}
在 Haskell 中,我们将其实现为
thoughtfulPerson :: String -> Animal
thoughtfulPerson originalThought newThought = "I normally think " ++ originalThought ", but I'm currently thinking" ++ newThought ++ "."
Java代码的“依赖注入”是通过Haskell的高阶函数实现的
方式四:组合优于继承+函数
假设我们有一个抽象基础 class 有两个方法的东西:
abstract class Thing {
public abstract String name();
public abstract void makeLightBlink(int duration);
}
我正在使用 Java 风格的语法,但希望它不会太混乱。
从根本上说,使用这个抽象基 class 的唯一方法是调用它的两个方法。因此,一个Thing实际上应该被认为是一个由字符串和函数组成的有序对
在像Haskell这样的函数式语言中,我们会写
data Thing = Thing { name :: String, makeLightsBlink :: Int -> IO () }
换句话说,一个“Thing”由两部分组成:一个名称,它是一个字符串,一个函数makeLightsBlink,它接受一个Int并输出一个“IO action”。这是 Haskell 处理 IO 的方式 - 通过类型系统。
不是定义 Thing 的子classes,Haskell 只是让您定义输出 Thing 的函数(或直接定义 Thing 本身)。因此,如果在 Java 中,您可以定义
class ConcreteThing extends Thing {
@Override
public String name() {
return "ConcreteThing";
}
@Override
public void makeLightsBlink(int duration) {
for (int i = 0; i < duration; i++) {
System.out.println("Lights are blinking!");
}
}
}
在 Haskell 中,您可以改为定义
concreteThing :: Thing
concreteThing = Thing { name = "ConcreteThing", makeLightsBlink = blinkFunction } where
blinkFunction duration = for_ [1..duration] . const $ putStrLn "Lights are blinking!"
无需做任何花哨的事情。您可以使用组合和函数来实现您想要的任何行为。
方法 5 - 完全避免多态性
这与面向对象编程中的“开放与封闭原则”相对应。
有时候,正确的做法实际上是完全避免多态性。例如,考虑如何在 Java.
中实现单链表
abstract class List<T> {
public abstract bool is_empty();
public abstract T head();
public abstract List<T> tail();
public int length() {
return empty() ? 0 : 1 + tail().length();
}
}
class EmptyList<T> {
@Override
public bool is_empty() {
return true;
}
@Override
public T head() {
throw new IllegalArgumentException("can't take head of empty list");
}
@Override
public List<T> tail() {
throw new IllegalArgumentException("can't take tail of empty list");
}
}
class NonEmptyList<T> {
private final T head;
private final List<T> tail;
public NonEmptyList(T head, List<T> tail) {
this.head = head;
this.tail = tail;
}
@Override
public bool is_empty() {
return false;
}
@Override
public T head() {
return self.head;
}
@Override
public List<T> tail() {
return self.tail;
}
}
然而,这实际上不是一个好的模型,因为您希望只有两种构建列表的方法——空方法和非空方法. Haskell 让你可以很简单地做到这一点。类似的 Haskell 代码是
data List t = EmptyList | NonEmptyList t (List t)
empty :: List t -> Bool
empty EmptyList = True
empty (NonEmptyList t listT) = False
head :: List t -> t
head EmptyList = error "can't take head of empty list"
head (NonEmptyList t listT) = t
tail :: List t -> List t
tail EmptyList = error "can't take tail of empty list"
tail (NonEmptyList t listT) = listT
length list = if empty list then 0 else 1 + length (tail list)
当然,在 Haskell 中,我们尽量避免“部分”函数 - 我们尽量确保每个函数始终 return 是一个值。因此,您不会看到很多 Haskell 用户正是出于这个原因实际使用“head”和“tail”函数——他们有时会出错。您会看到
定义的长度
length EmptyList = 0
length (NonEmptyList t listT) = 1 + length listT
使用模式匹配。
函数式编程语言的这一特性称为“代数数据类型”。非常有用。
希望我已经让您相信,函数式编程不仅可以让您实现许多面向对象的设计模式,而且实际上还可以让您以更简洁明了的形式表达相同的想法。
我在您的示例中添加了一些糖分,因为很难用您的函数证明以对象为中心的实现是合理的。
请注意,我写得不多Haskell,但我认为它是进行比较的正确语言。
我不建议直接比较纯 OO 语言和纯 FP 语言,因为这是浪费时间。如果您选择一门 FP 语言并学习如何从功能上思考,您将不会错过任何 OO 功能。
-- We define and create data of type Fish and Bird
data Fish = Fish String
nemo = Fish "Nemo";
data Bird = Bird String
tweety = Bird "Tweety"
-- We define how they can be displayed with the function `show`
instance Show Fish where
show (Fish name) = name ++ " the fish"
instance Show Bird where
show (Bird name) = name ++ " the bird"
{- We define how animals can think with the function `think`.
Both Fish and Bird will be Animals.
Notice how `show` dispatches to the correct implementation.
We need to add to the type signature the constraint that
animals are showable in order to use `show`.
-}
class Show a => Animal a where
think :: a -> String -> String
think animal thought =
show animal ++ " is thinking about " ++ thought
instance Animal Fish
instance Animal Bird
-- Same thing for Swimmer, only with Fish
class Show s => Swimmer s where
swim :: s -> String -> String
swim swimmer length =
show swimmer ++ " is swimming " ++ length
instance Swimmer Fish
-- Same thing for Singer, only with Bird
class Show s => Singer s where
sing :: s -> String
sing singer = show singer ++ " is singing"
instance Singer Bird
{- We define a function which applies to any animal.
The compiler can figure out that it takes any type
of the class Animal because we are using `think`.
-}
goToCollege animal = think animal "quantum physics"
-- we're printing the values to the console
main = do
-- prints "Nemo the fish is thinking about quantum physics"
print $ goToCollege nemo
-- prints "Nemo the fish is swimming 4 meters"
print $ swim nemo "4 meters"
-- prints "Tweety the bird is thinking about quantum physics"
print $ goToCollege tweety
-- prints "Tweety the bird is singing"
print $ sing tweety
我想知道它在 Clojure 中会是什么样子。它并不令人满意,因为 defprotocol
不提供默认实现,但话又说回来:我们不是将一种风格强加于一种不是为它设计的语言吗?
(defprotocol Show
(show [showable]))
(defprotocol Animal
(think [animal thought]))
(defn animal-think [animal thought]
(str (show animal) " is thinking about " thought))
(defprotocol Swimmer
(swim [swimmer length]))
(defprotocol Singer
(sing [singer]))
(defrecord Fish [name]
Show
(show [fish] (str (:name fish) " the fish"))
Animal
(think [a b] (animal-think a b))
Swimmer
(swim [swimmer length] (str (show swimmer) " is swimming " length)))
(defrecord Bird [name]
Show
(show [fish] (str (:name fish) " the bird"))
Animal
(think [a b] (animal-think a b))
Singer
(sing [singer] (str (show singer) " is singing")))
(defn goToCollege [animal]
(think animal "quantum physics"))
(def nemo (Fish. "Nemo"))
(def tweety (Bird. "Tweety"))
(println (goToCollege nemo))
(println (swim nemo "4 meters"))
(println (goToCollege tweety))
(println (sing tweety))
问题是你想要什么样的多态性。如果您只需要在编译时使用一些多态性,Haskell 的 typeclass
对于大多数情况来说几乎是完美的。
如果你想拥有 运行 时间的多态性(即根据 运行 时间类型动态切换行为),许多函数式编程语言不鼓励使用这种编程模式,因为它具有强大的泛型和类型类, 动态多态性并不总是必要的。
简而言之,如果语言支持子类型,你可以选择动态多态性,而在没有完整子类型的严格函数式语言中,你应该始终以函数式方式编程。最后,如果你仍然想要两者(动态多态性和强大的类型类),你可以尝试具有 traits 的语言,如 Scala
或 Rust
.
假设您在面向对象的应用程序中有这个:
module Talker
def talk(word)
puts word
end
end
module Swimmer
def swim(distance)
puts "swimming #{distance}"
end
end
class Organism
def initialize
rise
end
def rise
puts "hello world"
end
end
class Animal extends Organism
def think(something)
puts "think #{something}"
end
end
class Bird extends Animal
include Talker
end
class Fish extends Animal
include Swimmer
end
bird = new Bird
fish = new Fish
在此,您可以调用每个方法都是唯一的:
bird.talk("hello")
fish.swim(50)
但是你也可以调用相同的方法:
bird.think("fly")
fish.think("swim")
如果我有一个接受动物的函数,我可以调用 think 函数:
def experience(animal)
animal.think("one")
animal.think("two")
animal.think("one")
end
在伪函数式语言中,你基本上可以这样做:
function experience(animal) {
think(animal)
think(animal)
think(animal)
}
但不是真的,你必须检查类型:
function think(genericObject) {
if (genericObject is Animal) {
animalThink(genericObject)
} else if (genericObject is SomethingElse) {
somethingElseThink(genericObject)
}
}
那是因为,在实现你的“体验”功能的时候,你不只是想要体验动物,你还想要体验石头、树木等其他东西,只是它们的体验功能不同而已。
function experience(thing) {
move(thing)
move(thing)
move(thing)
}
function move(thing) {
case thing {
match Animal then animalMove(thing)
match Plant then plantMove(thing)
match Rock then rockMove(thing)
}
}
通过这种方式,您无法拥有完全可重用的函数,您的函数必须知道它将在某处接收的特定类型。
有什么方法可以避免这种情况并使其更像函数式语言中的 OO 多态性?
如果是这样,在高层次上,如果可以用函数式语言解决它,它是如何工作的?
- Achieving polymorphism in functional programming
- https://www.quora.com/How-is-polymorphism-used-in-functional-programming-languages
- https://wiki.haskell.org/OOP_vs_type_classes
函数式编程语言有多种实现多态性的方法。我将对比 Java(我最了解的 OOP 语言)和 Haskell(我最了解的函数式语言)。
方式一:“参数多态”
有了参数多态性,您根本不需要了解基础类型。例如,如果我有一个包含 T 类型元素的单链表,实际上我不需要知道任何有关 T 类型的信息就可以找到列表的长度。我会写类似
的东西length :: forall a . [a] -> Integer
length [] = 0
length (x:xs) = 1 + length xs
in Haskell(显然我想在实践中使用更好的算法,但你明白了)。请注意,列表元素的类型是什么并不重要;获取长度的代码是相同的。第一行是“类型签名”。它说对于每个类型 a,length 将采用 a 的列表并输出一个整数。
这不能用于太多“严重的多态性”,但绝对是一个强有力的开始。它大致对应于 Java 的泛型。
方式二:类型class式多态
即使像检查相等性这样良性的事情实际上也需要多态性。不同的类型需要不同的代码来检查相等性,并且对于某些类型(通常是函数),由于停机问题,检查相等性实际上是不可能的。因此,我们使用“type classes”。
假设我定义了一个包含 2 个元素的新类型,Bob 和 Larry。在 Haskell 中,这看起来像
data VeggieTalesStars = Bob | Larry
我希望能够比较 VeggieTalesStars 类型的两个元素是否相等。为此,我需要实现一个 Eq 实例。
instance Eq VeggieTalesStars where
Bob == Bob = True
Larry == Larry = True
Bob == Larry = False
Larry == Bob = False
请注意函数 (==) 具有类型签名
(==) :: forall b . Eq b => b -> b -> Bool
这意味着对于每个类型 b,如果 b 有一个 Eq 实例,那么 (==) 可以接受两个类型 b 的参数和 return 一个 Bool。
你可能不难猜到不等于函数 (/=) 也有类型签名
(/=) :: forall b . Eq b => b -> b -> Bool
因为 (/=) 由
定义x /= y = not (x == y)
当我们调用(/=)函数时,该函数会根据参数的类型部署正确版本的(==)函数。如果参数具有不同的类型,您将无法使用 (/=).
来比较它们Typeclass-style 多态性允许您执行以下操作:
class Animal b where
think :: b -> String -> String
-- we provide the default implementation
think b string = "think " ++ string
data Fish = Fish
data Bird = Bird
instance Animal Fish where
instance Animal Bird where
Fish 和 Bird 都实现了“Animal”类型class,因此我们可以在两者上调用 think 函数。也就是说,
>>> think Bird "thought"
"think thought"
>>> think Fish "thought"
"think thought"
此用例大致对应于 Java 接口 - 类型可以根据需要实现任意数量的类型 class。但是类型 classes 比接口强大得多。
方式 3:函数
如果你的对象只有一个方法,它也可能只是一个函数。这是避免继承层次结构的一种非常常见的方法 - 处理函数而不是 1-method 基础的继承者 class.
因此可以定义
type Animal = String -> String
basicAnimal :: Animal
basicAnimal thought = "think " ++ thought
“动物”实际上只是一种获取一根弦并产生另一根弦的方式。这将对应于 Java 代码
class Animal {
public String think(String thought) {
return "think " + thought;
}
}
假设在Java中,我们决定按如下方式实现动物的子class:
class ThoughtfulPerson extends Animal {
private final String thought;
public ThoughtfulPerson(final String thought) {
this.thought = thought;
}
@Override
public String think(String thought) {
System.out.println("I normally think " + this.thought ", but I'm currently thinking" + thought + ".");
}
}
在 Haskell 中,我们将其实现为
thoughtfulPerson :: String -> Animal
thoughtfulPerson originalThought newThought = "I normally think " ++ originalThought ", but I'm currently thinking" ++ newThought ++ "."
Java代码的“依赖注入”是通过Haskell的高阶函数实现的
方式四:组合优于继承+函数
假设我们有一个抽象基础 class 有两个方法的东西:
abstract class Thing {
public abstract String name();
public abstract void makeLightBlink(int duration);
}
我正在使用 Java 风格的语法,但希望它不会太混乱。
从根本上说,使用这个抽象基 class 的唯一方法是调用它的两个方法。因此,一个Thing实际上应该被认为是一个由字符串和函数组成的有序对
在像Haskell这样的函数式语言中,我们会写
data Thing = Thing { name :: String, makeLightsBlink :: Int -> IO () }
换句话说,一个“Thing”由两部分组成:一个名称,它是一个字符串,一个函数makeLightsBlink,它接受一个Int并输出一个“IO action”。这是 Haskell 处理 IO 的方式 - 通过类型系统。
不是定义 Thing 的子classes,Haskell 只是让您定义输出 Thing 的函数(或直接定义 Thing 本身)。因此,如果在 Java 中,您可以定义
class ConcreteThing extends Thing {
@Override
public String name() {
return "ConcreteThing";
}
@Override
public void makeLightsBlink(int duration) {
for (int i = 0; i < duration; i++) {
System.out.println("Lights are blinking!");
}
}
}
在 Haskell 中,您可以改为定义
concreteThing :: Thing
concreteThing = Thing { name = "ConcreteThing", makeLightsBlink = blinkFunction } where
blinkFunction duration = for_ [1..duration] . const $ putStrLn "Lights are blinking!"
无需做任何花哨的事情。您可以使用组合和函数来实现您想要的任何行为。
方法 5 - 完全避免多态性
这与面向对象编程中的“开放与封闭原则”相对应。
有时候,正确的做法实际上是完全避免多态性。例如,考虑如何在 Java.
中实现单链表abstract class List<T> {
public abstract bool is_empty();
public abstract T head();
public abstract List<T> tail();
public int length() {
return empty() ? 0 : 1 + tail().length();
}
}
class EmptyList<T> {
@Override
public bool is_empty() {
return true;
}
@Override
public T head() {
throw new IllegalArgumentException("can't take head of empty list");
}
@Override
public List<T> tail() {
throw new IllegalArgumentException("can't take tail of empty list");
}
}
class NonEmptyList<T> {
private final T head;
private final List<T> tail;
public NonEmptyList(T head, List<T> tail) {
this.head = head;
this.tail = tail;
}
@Override
public bool is_empty() {
return false;
}
@Override
public T head() {
return self.head;
}
@Override
public List<T> tail() {
return self.tail;
}
}
然而,这实际上不是一个好的模型,因为您希望只有两种构建列表的方法——空方法和非空方法. Haskell 让你可以很简单地做到这一点。类似的 Haskell 代码是
data List t = EmptyList | NonEmptyList t (List t)
empty :: List t -> Bool
empty EmptyList = True
empty (NonEmptyList t listT) = False
head :: List t -> t
head EmptyList = error "can't take head of empty list"
head (NonEmptyList t listT) = t
tail :: List t -> List t
tail EmptyList = error "can't take tail of empty list"
tail (NonEmptyList t listT) = listT
length list = if empty list then 0 else 1 + length (tail list)
当然,在 Haskell 中,我们尽量避免“部分”函数 - 我们尽量确保每个函数始终 return 是一个值。因此,您不会看到很多 Haskell 用户正是出于这个原因实际使用“head”和“tail”函数——他们有时会出错。您会看到
定义的长度length EmptyList = 0
length (NonEmptyList t listT) = 1 + length listT
使用模式匹配。
函数式编程语言的这一特性称为“代数数据类型”。非常有用。
希望我已经让您相信,函数式编程不仅可以让您实现许多面向对象的设计模式,而且实际上还可以让您以更简洁明了的形式表达相同的想法。
我在您的示例中添加了一些糖分,因为很难用您的函数证明以对象为中心的实现是合理的。
请注意,我写得不多Haskell,但我认为它是进行比较的正确语言。
我不建议直接比较纯 OO 语言和纯 FP 语言,因为这是浪费时间。如果您选择一门 FP 语言并学习如何从功能上思考,您将不会错过任何 OO 功能。
-- We define and create data of type Fish and Bird
data Fish = Fish String
nemo = Fish "Nemo";
data Bird = Bird String
tweety = Bird "Tweety"
-- We define how they can be displayed with the function `show`
instance Show Fish where
show (Fish name) = name ++ " the fish"
instance Show Bird where
show (Bird name) = name ++ " the bird"
{- We define how animals can think with the function `think`.
Both Fish and Bird will be Animals.
Notice how `show` dispatches to the correct implementation.
We need to add to the type signature the constraint that
animals are showable in order to use `show`.
-}
class Show a => Animal a where
think :: a -> String -> String
think animal thought =
show animal ++ " is thinking about " ++ thought
instance Animal Fish
instance Animal Bird
-- Same thing for Swimmer, only with Fish
class Show s => Swimmer s where
swim :: s -> String -> String
swim swimmer length =
show swimmer ++ " is swimming " ++ length
instance Swimmer Fish
-- Same thing for Singer, only with Bird
class Show s => Singer s where
sing :: s -> String
sing singer = show singer ++ " is singing"
instance Singer Bird
{- We define a function which applies to any animal.
The compiler can figure out that it takes any type
of the class Animal because we are using `think`.
-}
goToCollege animal = think animal "quantum physics"
-- we're printing the values to the console
main = do
-- prints "Nemo the fish is thinking about quantum physics"
print $ goToCollege nemo
-- prints "Nemo the fish is swimming 4 meters"
print $ swim nemo "4 meters"
-- prints "Tweety the bird is thinking about quantum physics"
print $ goToCollege tweety
-- prints "Tweety the bird is singing"
print $ sing tweety
我想知道它在 Clojure 中会是什么样子。它并不令人满意,因为 defprotocol
不提供默认实现,但话又说回来:我们不是将一种风格强加于一种不是为它设计的语言吗?
(defprotocol Show
(show [showable]))
(defprotocol Animal
(think [animal thought]))
(defn animal-think [animal thought]
(str (show animal) " is thinking about " thought))
(defprotocol Swimmer
(swim [swimmer length]))
(defprotocol Singer
(sing [singer]))
(defrecord Fish [name]
Show
(show [fish] (str (:name fish) " the fish"))
Animal
(think [a b] (animal-think a b))
Swimmer
(swim [swimmer length] (str (show swimmer) " is swimming " length)))
(defrecord Bird [name]
Show
(show [fish] (str (:name fish) " the bird"))
Animal
(think [a b] (animal-think a b))
Singer
(sing [singer] (str (show singer) " is singing")))
(defn goToCollege [animal]
(think animal "quantum physics"))
(def nemo (Fish. "Nemo"))
(def tweety (Bird. "Tweety"))
(println (goToCollege nemo))
(println (swim nemo "4 meters"))
(println (goToCollege tweety))
(println (sing tweety))
问题是你想要什么样的多态性。如果您只需要在编译时使用一些多态性,Haskell 的 typeclass
对于大多数情况来说几乎是完美的。
如果你想拥有 运行 时间的多态性(即根据 运行 时间类型动态切换行为),许多函数式编程语言不鼓励使用这种编程模式,因为它具有强大的泛型和类型类, 动态多态性并不总是必要的。
简而言之,如果语言支持子类型,你可以选择动态多态性,而在没有完整子类型的严格函数式语言中,你应该始终以函数式方式编程。最后,如果你仍然想要两者(动态多态性和强大的类型类),你可以尝试具有 traits 的语言,如 Scala
或 Rust
.