【问题标题】:How do I get around Go not having parametric polymorphism?我如何绕过没有参数多态性的 Go?
【发布时间】:2011-10-17 23:17:20
【问题描述】:

我是 Go 新手,但我读过 Go 常客不会错过参数多态性。每次我尝试学习一门新语言时,我都会使用the L99 list of problems 进行练习。

即使我尝试编写像第一个问题一样微不足道的问题(在 Go 中将是一条语句,获取切片的最后一个元素),我将如何将其编写为获取任何类型切片的函数并且(使用我上面引用的那条语句)返回该切片的最后一个元素?

我认为即使该语言没有参数多态性,也必须有一些惯用的“Go”方式来做到这一点,以便 Go 常客声称他们不会错过参数多态性。否则,如果示例比列表的最后一个元素更复杂,您将需要一个函数来执行每种类型的任务。

我错过了什么?

【问题讨论】:

标签: go parametric-polymorphism


【解决方案1】:

您引用了“99 lisp 问题”,但 Lisp 根本没有参数多态性或静态类型。

许多静态类型语言,如 Objective-C 和泛型之前的 Java,没有参数多态性。解决方案是只使用可以接受所有值的类型,在 Go 中为 interface{},并在需要从中获取某些特定类型时进行强制转换。

对于您的具体问题,如何采取“任何类型的切片”;不幸的是,没有专门包含切片的接口,因为切片没有任何方法;所以你会坚持使用interface{}。由于您有一个未知的切片类型,您需要使用反射(reflect 包)来执行所有切片操作,包括获取长度和容量、追加和访问特定索引处的元素。

另一种选择是,不要使用“任何类型的切片”,只需在所有代码中使用“接口切片{}”即[]interface{},然后您可以在其上使用普通切片运算符,您可以将任何元素放入,但在取出时投射。

【讨论】:

  • 感谢您提供此信息,今晚我会尝试一下,看看能否成功。
  • Clojure 具有参数多态性。而且,据我所知,Common Lisp 至少有某种类型的动态调度。
  • []interface{} 不是很慢吗?
  • 没有参数多态性(或替换)的语言并不是真正静态类型的。这就像假冒类型。
【解决方案2】:

如何返回切片的最后一个元素的 Go 方法是简单地将其内联写为表达式。例如:

var a []int
...
last := a[len(a)-1]

将简单的表达式 a[len(a)-1] 封装到泛型函数中是不必要的复杂化。

与 Lisp 不同,Go 不是纯粹的函数式语言。根据 99 个 Lisp 问题的列表评估 Go 可能具有欺骗性。 Go 是一种“系统编程语言”——列表操作、元编程、符号 AI 或其他适合 Lisp 的任务并不是 Go 的强项。

我将 Go 视为 具有垃圾收集和并发性的改进 C。 Go 不是来与 Lisp 竞争的。

【讨论】:

  • 这是我大约 14 秒前意识到的……哈哈。我尝试了上面的建议(使用来自该站点的一些指针——blog.golang.org/2011/09/laws-of-reflection.html),虽然我认为有可能做我想做的事,但我肯定想不通(正如你所说,它肯定过于复杂)。我认为我通常将这些问题用作学习工具的做法在这里失败了。
  • 请注意,如果你正在学习 C,它也会失败。Lisp 只是一种非常不同的语言,在 Lisp 中有意义的问题在 Go 中可能没有那么大的意义,采取Go 中任何切片的最后一个元素都非常简单,以至于编写一个“通用”函数来完成它是没有意义的。
  • 只是挑剔:我不认为 Go 是一种系统编程语言。 Go 没有足够的中级语言功能或条件编译工具。并且 Go 不能很好地与其他语言一起使用。 Go 专为编写适合在受控企业环境中运行的多线程服务器而设计,并且擅长于编写。 IE。 Go 是 Java 或 Erlang 的替代品,而不是 C 或 C++。 Go 与 C++ 的竞争只是因为它的存在阻止了人们将 C++ 用于不适当的任务。
【解决方案3】:

这听起来很像当我发现我在 C、fpc 或 delphi 等其他编程语言中为不同类型的不同数组多次编写相同的代码时。我为一种可能永远不会实现的语言发明了参数多态性,使用预处理器技巧并将其称为“包含文件参数多态性”作为概念证明,您实际上可以在不需要 OOP 或任何复杂的过程语言中实现参数多态性泛型系统。但是,使用预处理器是一种滥用形式,它只是为了用 FPC 来证明这个概念。

由于 Golang 不使用预处理器,因此您必须使用接口或指针并将类型作为参数发送。但即使使用指针仍然意味着您必须编写大量代码来转换它并使其全部工作。接口比指针好,因为指针不太安全。

这样的解决方案:

last := a[len(a)-1]

容易出现错误,因为有人可能会忘记负 1。有些语言的情况稍好一些:

// return last element, the "high" of a
last := a[high(a)]
// return first element, the "low" of a
first := a[low(a)]

上述代码在 Go AFAIK 中不起作用(尚未研究 Go 是否有类似的东西),这只是一些其他语言(fpc)可能是 Go 考虑的东西。

这种处理事物的低和高方式绝对可以确保选择最后一个和第一个元素,而使用“减一”容易产生基本的数学错误。有人可能会忘记减一...因为他们对基于 1 的数组与基于零的数组感到困惑。即使该语言没有基于 1 的数组之类的东西,由于人类有时会以基于 1 的方式思考(我们的手指从 1 开始,而不是 0),仍然可能会出错。一些聪明的程序员会争辩说,不,我们的手指从零开始,而不是从一开始。你的拇指为零。好吧,很好..但是..对于世界上的大多数人来说......;-) 我们最终在现实世界与计算机世界中整天从 1 到 0 来回切换我们的大脑,这导致了许多软件中的错误。

但有些人会认为“低”和“高”只是语法糖,在最小语言中不是必需的。必须决定额外的安全是否值得,在许多情况下是值得的。我不确定 LOW() 和 HIGH() 给编译器增加了多少复杂性,以及它如何影响性能。我不是 100% 确定……我认为编译器可以很聪明地优化高低,但我不确定。

【讨论】:

    【解决方案4】:

    只是回答如何获取数组的最后一个(也是第一个)元素的问题,这是 Go 中的正确方法:

    last := a[:1] first := a[1:]

    但这与参数多态无关,即类型推断,在编译时计算。

    我正在尝试编写一个二叉树库,我仍在努力寻找最有效、可读性和性能最好的方法来抽象数据类型,具体来说,我有存储、游标和索引映射系统编写和 walk 函数,但我希望能够切换出实际存储在节点中的数据类型。我在这个过程中学到了很多关于作曲和嵌入的知识,但这并没有让我完全开心。

    我确实对函数式编程的原理知之甚少,而 Go 恰好将函数视为第一类,因此理论上可能存在针对参数多态性问题的函数式解决方案。我正在解决这个问题,因为基本上我喜欢函数范式,但无论如何我讨厌递归(我更喜欢迭代,对我来说更容易可视化 100 倍)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-12
      • 1970-01-01
      • 1970-01-01
      • 2016-03-19
      • 2018-07-11
      • 2016-09-12
      • 1970-01-01
      相关资源
      最近更新 更多