如何使深度嵌套的函数调用多态?
How to make deeply nested function call polymorphic?
所以我有一个自定义的编程语言,我在其中做一些数学运算 formalization/modeling。在这种情况下,我基本上是这样做的(伪 javascript 表示):
isIntersection([1, 2, 3], [1, 2], [2, 3]) // => true
isIntersection([1, 2, 3], [1, 2, 3], [3, 4, 5]) // => false
function isIntersection(setTest, setA, setB) {
i = 0
while (i < setTest.length) {
let t = setTest[i]
if (includes(setA, t) || includes(setB, t)) {
i++
} else {
return false
}
}
return true
}
function includes(set, element) {
for (x in set) {
if (isEqual(element, x)) {
return true
}
}
return false
}
function isEqual(a, b) {
if (a is Set && b is Set) {
return isSetEqual(a, b)
} else if (a is X... && b is X...) {
return isX...Equal(a, b)
} ... {
...
}
}
function isSetEqual(a, b) {
i = 0
while (i < a.length) {
let x = a[i]
let y = b[i]
if (!isEqual(x, y)) {
return false
}
i++
}
return true
}
isIntersection
正在检查isEqual
,isEqual
配置为能够处理各种相等检查的情况,从集合与集合,对象到对象, X 到 X 等..
问题是,我们怎样才能让 isEqual
不知道实现细节呢?现在你必须对每一种可能的对象类型都有一个大的 if/else/switch 语句。如果我们添加一个新类型,我们必须修改这个巨大的 isEqual
方法来添加对它的支持。我们怎样才能避免这种情况,而只是分别和干净地定义它们?
我最初想的是使对象成为“classes 的实例”,可以说,使用 class 方法。但我喜欢一切都只是函数和结构(没有方法的对象)的纯粹性。有没有什么方法可以在不使用 classes with 方法的情况下实现这种事情,而不是只保留函数和对象?
如果没有,那么您将如何用 classes 实现它?会不会只是这样?
class Set {
isEqual(set) {
i = 0
while (i < this.length) {
let x = this[i]
let y = set[i]
if (!x.isEqual(y)) {
return false
}
i++
}
return true
}
}
这意味着每个对象都必须在其上定义 isEqual
。 Haskell 如何处理这样的系统?基本上是在寻找如何最干净地完成这项工作的灵感。我想理想地避免使用方法 classes。
注意:您不能只委托给 ==
本机实现(假设这是在 JavaScript 中)。我们使用的是自定义编程语言,基本上是首先尝试定义 ==
的含义。
另一种方法是以某种方式将 isEqual
函数与所有内容一起传递,但我真的不知道该怎么做,如果可能的话,它会很笨重。所以不确定最好的方法是什么。
Haskell 利用其类型和类型-class 系统来处理多态相等性。
相关代码为
class Eq a where
(==) :: a -> a -> Bool
英文翻译是:一个类型 a
实现 Eq
class 当且仅当它定义一个函数 (==)
接受两个类型的输入a
并输出 Bool
.
通常,我们会声明类型classes 应遵守的某些“法律”。例如,x == y
在所有情况下都应与 y == x
相同,而 x == x
永远不应与 False
相同。编译器无法检查这些定律,因此通常只是将它们写入文档。
一旦我们以上述方式定义了类型 class Eq
,我们就可以访问 (==)
函数(可以使用中缀表示法调用 - 即,我们可以写 (==) x y
或 x == y
)。这个函数的类型是
(==) :: forall a . Eq a => a -> a -> Bool
换句话说,对于每个实现类型 class Eq
的 a
,(==)
都是 a -> a -> Bool
.
类型
考虑一个示例类型
data Boring = Dull | Uninteresting
类型 Boring
有两个正确的值,Dull
和 Uninteresting
。我们可以定义 Eq
实现如下:
instance Eq Boring where
Dull == Dull = True
Dull == Uninteresting = False
Uninteresting == Uninteresting = True
Uninteresting == Dull = False
现在,我们将能够评估两个 Boring
类型的元素是否相等。
ghci> Dull == Dull
True
ghci> Dull == Uninteresting
False
请注意,这与 Javascript 的平等概念非常不同。无法使用 (==)
比较不同类型的元素。例如,
ghci> Dull == 'w'
<interactive>:146:9: error:
* Couldn't match expected type `Boring' with actual type `Char'
* In the second argument of `(==)', namely 'w'
In the expression: Dull == 'w'
In an equation for `it': it = Dull == 'w'
当我们尝试将 Dull
与字符 'w'
进行比较时,我们会收到类型错误,因为 Boring
和 Char
是不同的类型。
我们可以这样定义
includes :: Eq a => [a] -> a -> Bool
includes [] _ = False
includes (x:xs) element = element == x || includes xs element
我们读这个定义如下:
includes
是一个函数,对于任何实现相等性测试的类型 a
,获取 a
列表和单个 a
并检查是否元素在列表中。
如果列表为空,则 includes list element
将计算为 False
。
如果列表不为空,我们将列表写成x : xs
(第一个元素为x
,其余元素为xs
的列表)。那么 x:xs
包含 element
当且仅当 x
等于 element
,或者 xs
包含 element
.
我们还可以定义
instance Eq a => Eq [a] where
[] == [] = True
[] == (_:_) = False
(_:_) == [] = False
(x:xs) == (y:ys) = x == y && xs == ys
这段代码的英文翻译是:
考虑任何类型 a
使得 a
实现 Eq
class(换句话说,(==)
被定义为类型 a
).然后 [a]
也实现了 Eq
类型 class - 也就是说,我们可以在两个 [a]
.
类型的值上使用 (==)
[a]
实现类型class的方式如下:
空列表等于它自己。
空列表不等于非空列表。
要确定两个非空列表 (x:xs)
和 (y:ys)
是否相等,请检查它们的第一个元素是否相等(也就是 x == y
是否相等)。如果第一个元素相等,则递归检查剩余元素是否相等(是否xs == ys
)。如果这两个都为真,则两个列表相等。否则,它们不相等。
请注意,我们实际上在 Eq [a]
的实现中使用了两个不同的 ==
。等式 x == y
使用 Eq a
实例,而等式 xs == ys
递归使用 Eq [a]
实例。
实际上,定义 Eq
个实例通常非常简单,Haskell 让编译器来完成这项工作。例如,如果我们改写
data Boring = Dull | Uninteresting deriving (Eq)
Haskell 会自动为我们生成 Eq Boring
实例。 Haskell 还可以让我们派生出其他类型 class,例如 Ord
(其中定义了函数 (<)
和 (>)
),show
(允许我们将我们的数据转换为 String
s) 和 read
(这允许我们将 String
s 转换回我们的数据类型)。
请记住,这种方法在很大程度上依赖于静态类型和类型检查。 Haskell 确保我们只在比较相同类型的元素时使用 (==)
函数。编译器也总是知道在任何给定情况下使用 (==)
的编译类型定义,因为它知道要比较的值的类型,因此不需要进行任何类型的动态调度(尽管有一些情况编译器将选择进行动态分派的位置)。
如果您的语言使用动态类型,则此方法将不起作用,并且如果您希望能够定义新类型,您将不得不使用某种动态分派。如果你使用静态类型,你一定要看看 Haskell 的类型 class 系统。
所以我有一个自定义的编程语言,我在其中做一些数学运算 formalization/modeling。在这种情况下,我基本上是这样做的(伪 javascript 表示):
isIntersection([1, 2, 3], [1, 2], [2, 3]) // => true
isIntersection([1, 2, 3], [1, 2, 3], [3, 4, 5]) // => false
function isIntersection(setTest, setA, setB) {
i = 0
while (i < setTest.length) {
let t = setTest[i]
if (includes(setA, t) || includes(setB, t)) {
i++
} else {
return false
}
}
return true
}
function includes(set, element) {
for (x in set) {
if (isEqual(element, x)) {
return true
}
}
return false
}
function isEqual(a, b) {
if (a is Set && b is Set) {
return isSetEqual(a, b)
} else if (a is X... && b is X...) {
return isX...Equal(a, b)
} ... {
...
}
}
function isSetEqual(a, b) {
i = 0
while (i < a.length) {
let x = a[i]
let y = b[i]
if (!isEqual(x, y)) {
return false
}
i++
}
return true
}
isIntersection
正在检查isEqual
,isEqual
配置为能够处理各种相等检查的情况,从集合与集合,对象到对象, X 到 X 等..
问题是,我们怎样才能让 isEqual
不知道实现细节呢?现在你必须对每一种可能的对象类型都有一个大的 if/else/switch 语句。如果我们添加一个新类型,我们必须修改这个巨大的 isEqual
方法来添加对它的支持。我们怎样才能避免这种情况,而只是分别和干净地定义它们?
我最初想的是使对象成为“classes 的实例”,可以说,使用 class 方法。但我喜欢一切都只是函数和结构(没有方法的对象)的纯粹性。有没有什么方法可以在不使用 classes with 方法的情况下实现这种事情,而不是只保留函数和对象?
如果没有,那么您将如何用 classes 实现它?会不会只是这样?
class Set {
isEqual(set) {
i = 0
while (i < this.length) {
let x = this[i]
let y = set[i]
if (!x.isEqual(y)) {
return false
}
i++
}
return true
}
}
这意味着每个对象都必须在其上定义 isEqual
。 Haskell 如何处理这样的系统?基本上是在寻找如何最干净地完成这项工作的灵感。我想理想地避免使用方法 classes。
注意:您不能只委托给 ==
本机实现(假设这是在 JavaScript 中)。我们使用的是自定义编程语言,基本上是首先尝试定义 ==
的含义。
另一种方法是以某种方式将 isEqual
函数与所有内容一起传递,但我真的不知道该怎么做,如果可能的话,它会很笨重。所以不确定最好的方法是什么。
Haskell 利用其类型和类型-class 系统来处理多态相等性。
相关代码为
class Eq a where
(==) :: a -> a -> Bool
英文翻译是:一个类型 a
实现 Eq
class 当且仅当它定义一个函数 (==)
接受两个类型的输入a
并输出 Bool
.
通常,我们会声明类型classes 应遵守的某些“法律”。例如,x == y
在所有情况下都应与 y == x
相同,而 x == x
永远不应与 False
相同。编译器无法检查这些定律,因此通常只是将它们写入文档。
一旦我们以上述方式定义了类型 class Eq
,我们就可以访问 (==)
函数(可以使用中缀表示法调用 - 即,我们可以写 (==) x y
或 x == y
)。这个函数的类型是
(==) :: forall a . Eq a => a -> a -> Bool
换句话说,对于每个实现类型 class Eq
的 a
,(==)
都是 a -> a -> Bool
.
考虑一个示例类型
data Boring = Dull | Uninteresting
类型 Boring
有两个正确的值,Dull
和 Uninteresting
。我们可以定义 Eq
实现如下:
instance Eq Boring where
Dull == Dull = True
Dull == Uninteresting = False
Uninteresting == Uninteresting = True
Uninteresting == Dull = False
现在,我们将能够评估两个 Boring
类型的元素是否相等。
ghci> Dull == Dull
True
ghci> Dull == Uninteresting
False
请注意,这与 Javascript 的平等概念非常不同。无法使用 (==)
比较不同类型的元素。例如,
ghci> Dull == 'w'
<interactive>:146:9: error:
* Couldn't match expected type `Boring' with actual type `Char'
* In the second argument of `(==)', namely 'w'
In the expression: Dull == 'w'
In an equation for `it': it = Dull == 'w'
当我们尝试将 Dull
与字符 'w'
进行比较时,我们会收到类型错误,因为 Boring
和 Char
是不同的类型。
我们可以这样定义
includes :: Eq a => [a] -> a -> Bool
includes [] _ = False
includes (x:xs) element = element == x || includes xs element
我们读这个定义如下:
includes
是一个函数,对于任何实现相等性测试的类型 a
,获取 a
列表和单个 a
并检查是否元素在列表中。
如果列表为空,则 includes list element
将计算为 False
。
如果列表不为空,我们将列表写成x : xs
(第一个元素为x
,其余元素为xs
的列表)。那么 x:xs
包含 element
当且仅当 x
等于 element
,或者 xs
包含 element
.
我们还可以定义
instance Eq a => Eq [a] where
[] == [] = True
[] == (_:_) = False
(_:_) == [] = False
(x:xs) == (y:ys) = x == y && xs == ys
这段代码的英文翻译是:
考虑任何类型 a
使得 a
实现 Eq
class(换句话说,(==)
被定义为类型 a
).然后 [a]
也实现了 Eq
类型 class - 也就是说,我们可以在两个 [a]
.
(==)
[a]
实现类型class的方式如下:
空列表等于它自己。
空列表不等于非空列表。
要确定两个非空列表 (x:xs)
和 (y:ys)
是否相等,请检查它们的第一个元素是否相等(也就是 x == y
是否相等)。如果第一个元素相等,则递归检查剩余元素是否相等(是否xs == ys
)。如果这两个都为真,则两个列表相等。否则,它们不相等。
请注意,我们实际上在 Eq [a]
的实现中使用了两个不同的 ==
。等式 x == y
使用 Eq a
实例,而等式 xs == ys
递归使用 Eq [a]
实例。
实际上,定义 Eq
个实例通常非常简单,Haskell 让编译器来完成这项工作。例如,如果我们改写
data Boring = Dull | Uninteresting deriving (Eq)
Haskell 会自动为我们生成 Eq Boring
实例。 Haskell 还可以让我们派生出其他类型 class,例如 Ord
(其中定义了函数 (<)
和 (>)
),show
(允许我们将我们的数据转换为 String
s) 和 read
(这允许我们将 String
s 转换回我们的数据类型)。
请记住,这种方法在很大程度上依赖于静态类型和类型检查。 Haskell 确保我们只在比较相同类型的元素时使用 (==)
函数。编译器也总是知道在任何给定情况下使用 (==)
的编译类型定义,因为它知道要比较的值的类型,因此不需要进行任何类型的动态调度(尽管有一些情况编译器将选择进行动态分派的位置)。
如果您的语言使用动态类型,则此方法将不起作用,并且如果您希望能够定义新类型,您将不得不使用某种动态分派。如果你使用静态类型,你一定要看看 Haskell 的类型 class 系统。