如何使深度嵌套的函数调用多态?

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正在检查isEqualisEqual配置为能够处理各种相等检查的情况,从集合与集合,对象到对象, 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 yx == y)。这个函数的类型是

(==) :: forall a . Eq a => a -> a -> Bool

换句话说,对于每个实现类型 class Eqa(==) 都是 a -> a -> Bool.

类型

考虑一个示例类型

data Boring = Dull | Uninteresting

类型 Boring 有两个正确的值,DullUninteresting。我们可以定义 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' 进行比较时,我们会收到类型错误,因为 BoringChar 是不同的类型。

我们可以这样定义

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(允许我们将我们的数据转换为 Strings) 和 read(这允许我们将 Strings 转换回我们的数据类型)。

请记住,这种方法在很大程度上依赖于静态类型和类型检查。 Haskell 确保我们只在比较相同类型的元素时使用 (==) 函数。编译器也总是知道在任何给定情况下使用 (==) 的编译类型定义,因为它知道要比较的值的类型,因此不需要进行任何类型的动态调度(尽管有一些情况编译器将选择进行动态分派的位置)。

如果您的语言使用动态类型,则此方法将不起作用,并且如果您希望能够定义新类型,您将不得不使用某种动态分派。如果你使用静态类型,你一定要看看 Haskell 的类型 class 系统。