如何解决 Go 没有参数多态性的问题?
我是一个 Go 新手,但我读到 Go 常客不会错过参数多态性。每次我尝试学习一门新语言时,我都会使用 L99问题列表以获得一些练习。
即使我尝试编写像第一个问题一样简单的东西(在 Go 中将是一个单独的语句,获取切片的最后一个元素),我如何将其编写为一个函数,该函数获取任何类型的切片并且(使用我上面引用的单个语句)返回该切片的最后一个元素?
我认为即使该语言没有参数多态性,也必须有一些惯用的“Go”方式来做到这一点,以便 Go 常规者声称他们不会错过参数多态性。否则,如果示例比列表的最后一个元素更复杂,则您将需要一个函数来为每种类型执行任务。
我缺少什么?
I'm a Go newcomer, but I have read that Go regulars do not miss parametric polymorphism. Every time I try to learn a new language I use the L99 list of problems to get some practice.
Even if I try to write something as trivial as the first problem (which in Go would be a single statement, taking the last element of a slice), how would I write this as a function that takes a slice of any type and (using that single statement I referenced above) returns the last element of that slice?
I figured even though the language does not have parametric polymorphism there must be some idiomatic 'Go' way of doing this in order for Go regulars to claim they dont miss parametric polymorphism. Otherwise, if the example were more complex than just the last element of a list for instance, you would need a function to perform your task for every type.
What am I missing?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
你引用了“99 lisp 问题”,但 Lisp 根本没有参数多态性或静态类型。
许多静态类型语言,如 Objective-C 和泛型之前的 Java,没有参数多态性。解决方案是只使用可以接受所有值的类型,在 Go 中是
interface{}
,并在需要从中获取某些特定类型时进行强制转换。对于您的具体问题,如何采取“任何类型的切片”;不幸的是,没有专门包含切片的接口,因为切片没有任何方法;因此您将不得不使用
interface{}
。由于切片类型未知,因此需要使用反射(reflect
包)来执行所有切片操作,包括获取长度和容量、附加以及访问特定索引处的元素。另一种选择是,在所有代码中不使用“任何类型的切片”,而只使用“interface{} 的切片”,即
[]interface{}
,然后您可以使用普通的切片运算符在上面,你可以放入任何元素,但当你取出它们时就会进行投射。You cite the "99 lisp problems", yet Lisp does not have parametric polymorphism or static types at all.
Many statically-typed languages, like Objective-C, and Java before generics, have no parametric polymorphism. The solution is to just use a type that can accept all values, which in Go is
interface{}
, and cast when you need to get some specific type out of it.For your specific question, how to take "any type of slice"; unfortunately, there is no interface that includes specifically slices, since slices do not have any methods; so you'll be stuck with using
interface{}
. Since you have an unknown slice type, you need to use reflection (thereflect
package) to perform all the slice operations, including getting the length and capacity, appending, and accessing the element at a particular index.Another alternative is that instead of using "slice of any type", just use "slice of interface{}" i.e.
[]interface{}
, in all your code, then you can use the normal slice operators on it, and you can put any elements in, but cast when you get them out.Go 返回切片最后一个元素的方法是简单地将其作为表达式内联写入。例如:
将简单表达式
a[len(a)-1]
封装到泛型函数中是不必要的复杂化。与 Lisp 不同,Go 不是一种纯粹的函数式语言。根据 99 个 Lisp 问题的列表来评估 Go 可能具有欺骗性。 Go 是一种“系统编程语言”——列表操作、元编程、符号 AI 或其他适合 Lisp 的任务并不是 Go 的强项。
我将 Go 视为具有垃圾收集和并发性的改进的 C。 Go 并不是来与 Lisp 竞争的。
The Go way of how to return the last element of a slice is to simply write it inline as an expression. For example:
Encapsulating the simple expression
a[len(a)-1]
into a generic function is an unnecessary complication.Unlike Lisp, Go isn't a purely functional language. Evaluating Go based on a list of 99 Lisp problems may be deceiving. Go is a "systems programming language" - list manipulation, meta-programming, symbolic AI, or other Lisp-suited tasks aren't Go's strong sides.
I view Go as an improved C with garbage-collection and concurrency. Go isn't here to compete with Lisp.
这听起来很像当我发现我用其他编程语言(如 C、fpc 或 delphi)为不同类型的不同数组多次编写相同的代码时。我为一种可能永远不会实现的语言发明了参数多态性,使用预处理器技巧并将其称为“包含文件参数多态性”作为概念证明,您实际上可以将参数多态性实现到过程语言中,而不需要 OOP 或任何复杂的东西仿制药系统。使用预处理器虽然是一种滥用,但这只是为了用 FPC 来证明这个概念。
由于 Golang 不使用预处理器,因此您必须使用接口或指针并将类型作为参数发送。但即使使用指针仍然意味着您必须编写大量代码来转换它并使其全部工作。接口比指针更好,因为指针不太安全。
像这样的解决方案:
容易出现错误,因为有人可能会忘记负数1。有些语言有一些更好的东西:
上面的代码在 Go AFAIK 中不起作用(还没有研究 go 是否有类似的东西),这只是一些其他语言有(fpc),Go 可能会考虑这一点。
这种处理事物的低和高方式绝对确保选择最后一个和第一个元素,而使用“减一”很容易出现基本的数学错误。有人可能会忘记负一...因为他们对基于 1 的数组和基于零的数组感到困惑。即使该语言没有基于 1 的数组之类的东西,人们仍然可能会犯错误,因为人类有时会以基于 1 的方式思考(我们的手指从 1 开始,而不是 0)。一些聪明的程序员会争辩说,不,我们的手指从零开始,而不是从一开始。你的拇指为零。好吧,好吧……但是……对于世界上的大多数人来说……;-)我们最终会在现实世界和计算机世界中整天从 1 基到 0 基来来回切换我们的大脑,这会导致许多软件中的错误。
但有些人会认为“低”和“高”只是语法糖,在极简语言中不是必需的。必须确定额外的安全是否值得,在许多情况下是值得的。我不确定 LOW() 和 HIGH() 会给编译器增加多少复杂性,以及它如何影响性能..我不是 100% 确定...我认为编译器可以智能地优化高和低,但我不确定。
This sounds a lot like when I discovered I was writing the same code multiple times for different arrays of different types in other programming languages like C, fpc, or delphi. I invented parametric polymorphism for a language that will probably never have it implemented, using preprocessor tricks and called it "include file parametric polymorphism" as a proof of concept that you could in fact implement parametric polymorphism into a procedural language without needing OOP or any complex generics system. Using the preprocessor is a form of abuse though, it was just to prove the concept with FPC.
Since Golang doesn't use a preprocessor, you'll have to use interfaces or pointers and send the type in as a parameter. But even using pointers still means you have to write a lot of code to cast it and make it all work. Interfaces are better than pointers because pointers are less safe.
Solutions like this:
Are prone to bugs because someone may forget the minus 1. Some languages have something slightly better:
Above code doesn't work in Go AFAIK (haven't researched whether go has something similar to this), it's just what some other languages have (fpc) that might be something Go considers.
This low and high way of dealing with things absolutely ensures the last and first element are chosen whereas using "minus one" is prone to making basic math errors. Someone may forget the minus one...because they got confused about 1 based array, versus zero based arrays. Even if the language doesn't have a such thing as a 1 based array, one could still make the error due to humans sometimes thinking in 1 based ways (our fingers start at 1, not 0). Some clever programmers would argue that, no, our fingers start at zero, not one. Your thumb is zero. Okay, fine.. but.. for most of the world...;-) we end up switching back and forth our brains from 1 based to 0 based all day long in the real world vs the computer world, and this causes numerous bugs in software.
But some would argue "Low" and "High" are just syntactic sugar that is not necessary in a minimal language. It has to be decided whether the extra safety is worthwhile, which in many cases it can be. How much complexity LOW() and HIGH() adds to a compiler I'm not sure, and how it affects performance.. I'm not 100 percent sure... I think the compiler can be smart about optimizing high and low, but I'm not certain.
只是回答如何获取数组的最后一个(和第一个)元素的问题,这是 Go 中的正确方法:
最后 := a[:1]
第一个 := a[1:]
但这与参数多态性无关,参数多态性是类型推断,是在编译时计算的。
我正在尝试编写二叉树库,并且仍在努力寻找最有效、可读和高性能的方法来抽象数据类型,具体来说,我编写了存储、游标和索引映射系统,并且walk 函数,但我希望能够切换实际存储在节点中的数据类型。我在这个过程中学到了很多关于组合和嵌入的知识,但这并没有让我完全高兴。
我确实了解一些函数式编程的原理,并且 Go 恰好将函数视为一流的,因此理论上可能存在参数多态性问题的函数式解决方案。我正在弄清楚它,因为基本上我喜欢函数范式,但无论如何我讨厌递归(我更喜欢迭代,对我来说更容易想象 100 倍)。
Just answering the question of how to get the last (and first) element of an array, this is the proper way in Go:
last := a[:1]
first := a[1:]
But this has nothing to do with parametric polymorphism, that is type inference and is computed at compile time.
I am in the process of attempting to write a binary tree library and I'm still struggling with the most efficient and readable and performant way to abstract a data type, specifically, I have the store, the cursor and index map system written, and walk functions, but I want to be able to switch out the data type that is actually stored in the nodes. I learned a lot about composition and embedding in the process but it's not making me completely happy.
I do know a little of the principles of functional programming and Go happens to treat functions as first class so in theory there probably is a functional solution to the parametric polymorphism issue. I am in the process of figuring it out because basically I love the Functional paradigm, but I hate recursion anyway (I prefer iteration, 100x easier for me to visualise).