Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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.

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.

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).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top