英文:
How do I get around Go not having parametric polymorphism?
问题
我是一名Go的新手,但我已经读到Go的老手不会错过参数多态性。每次我尝试学习一门新语言时,我都会使用L99问题列表来进行一些练习。
即使我试图写一些像第一个问题那样微不足道的东西(在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?
答案1
得分: 13
你引用了“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 (the reflect
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.
答案2
得分: 8
The Go way of how to return the last element of a slice is to simply write it inline as an expression. For example:
var a []int
...
last := a[len(a)-1]
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.
英文:
The Go way of how to return the last element of a slice is to simply write it inline as an expression. For example:
var a []int
...
last := a[len(a)-1]
Encapsulating the simple expression <code>a[len(a)-1]</code> 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.
答案3
得分: 1
这听起来很像当我发现我在其他编程语言(如C、fpc或delphi)中为不同类型的不同数组多次编写相同的代码时。我发明了一种参数化多态性,用预处理器技巧实现了一种可能永远不会实现的语言,并将其称为“包含文件参数化多态性”,作为一个概念验证,证明你实际上可以将参数化多态性实现到过程化语言中,而不需要面向对象编程或任何复杂的泛型系统。虽然使用预处理器是一种滥用的形式,但这只是为了用FPC证明这个概念。
由于Golang不使用预处理器,您将不得不使用接口或指针,并将类型作为参数传递。但即使使用指针,仍然意味着您必须编写大量的代码来进行类型转换并使其正常工作。接口比指针更好,因为指针不太安全。
像这样的解决方案:
last := a[len(a)-1]
容易出现错误,因为有人可能忘记减1。一些语言有稍微好一些的解决方案:
// 返回最后一个元素,a的“高”
last := a[high(a)]
// 返回第一个元素,a的“低”
first := a[low(a)]
上述代码在Go中不起作用(我不确定Go是否有类似的东西),这只是一些其他语言(fpc)可能考虑的东西。
这种处理方式绝对确保选择最后一个和第一个元素,而使用“减一”容易出现基本数学错误。有人可能会忘记减一...因为他们对基于1的数组和基于零的数组感到困惑。即使语言没有基于1的数组,由于人类有时以基于1的方式思考(我们的手指从1开始,而不是0),仍然可能犯错误。一些聪明的程序员会争辩说,不,我们的手指从零开始,而不是从一开始。好吧,但是...对于大多数世界来说...;-)我们在现实世界和计算机世界中不断在1和0之间切换我们的大脑,这导致软件中出现了许多错误。
但是有人会争论“低”和“高”只是一种在最小化语言中不必要的语法糖。必须决定额外的安全性是否值得,对于许多情况来说,它可能是值得的。我不确定LOW()和HIGH()对编译器增加了多少复杂性,以及它如何影响性能...我不确定...我认为编译器可以聪明地优化高和低,但我不确定。
英文:
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:
last := a[len(a)-1]
Are prone to bugs because someone may forget the minus 1. Some languages have something slightly better:
// return last element, the "high" of a
last := a[high(a)]
// return first element, the "low" of a
first := a[low(a)]
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.
答案4
得分: 0
只回答如何获取数组的最后一个(和第一个)元素的问题,这是在Go中的正确方法:
last := a[:1]
first := a[1:]
但这与参数化多态无关,参数化多态是类型推断,并在编译时计算。
我正在尝试编写一个二叉树库,我仍然在努力寻找最高效、可读性和性能的方式来抽象数据类型,具体来说,我已经编写了存储、游标和索引映射系统以及遍历函数,但我希望能够切换实际存储在节点中的数据类型。在这个过程中,我学到了很多关于组合和嵌入的知识,但这并没有完全让我满意。
我对函数式编程的原则有一些了解,而Go恰好将函数视为一等公民,所以理论上可能有一种函数式解决方案来解决参数化多态的问题。我正在努力弄清楚这个问题,因为基本上我喜欢函数式范式,但无论如何我都不喜欢递归(我更喜欢迭代,对我来说更容易可视化)。
英文:
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).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论