Golang Generics: Difference between revisions

From Adam Colton
Jump to navigation Jump to search
Created page with "I am in the camp of those mostly opposed to generics. Maybe something innovative would convince me, but dropping in C++ style generics is something I strongly oppose. Let's s..."
 
No edit summary
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
I am in the camp of those mostly opposed to generics. Maybe something innovative would convince me, but dropping in C++ style generics is something I strongly oppose.
Title: Generic: opposition and proposal
 
I quit Go over the lack of generics once. I learned Go for the first time in 2012. After a few weeks of playing around with the language, I decided that it was terribly designed, overly opinionated, too cumbersome and the lack of generics showed just how immature it was. And I quit. For about a year.
 
But I kept coming back to the few things I liked. I liked the simplicity. I liked the compilation speed. I liked the run speed (I was coming mostly from Python and PHP). And I really liked the concurrency. So after a year, I decided I'd come back, and I'd just hack my way around the shortcomings of the language. The first two things I did when I came back were figure out the reflection library and figured out how to abuse "unsafe" to get around most of the language constraints (for instance, you can gain access to and modify unexported fields with unsafe). Thus armed, I was prepared to do battle with the compiler.
 
Then something funny started to happen. The things I hated started to make sense. The compiler would warn me about an unused variable, so I'd go to do what I always did (some sort of no op to trick it) only to find what would have been a subtle bug or something I forgot. Maybe I accidentally used := inside an if block when I meant =. Or maybe I set an index variable in a range loop and then forgot about the code where I meant to use it. And my finely honed toolset for beating the compiler into submission sat unused. Real problem after real problem fell before maps and slices.
 
Then I started to really understand interfaces. I came to the language thinking "Oh, so they just all objects structs and abstract objects interfaces, only they're both stupid". But I was being stupid. It took a while, but I started to see the patterns that arise from structs and interfaces. To me, they started to seem simpler and more powerful than the patterns I new from classic OOP.
 
And the final nail in the coffin was when I realized that I almost never had trouble reading code other people had written in Go. And not just reading it, but being able to understand exactly how it worked. A sort of "nulles in verba" - I didn't have to trust that the code did what the semantics (variable and function names) indicated, I could easily trace through the static structures of the code and confirm for myself. Because it's hard to do "magic" in Go and in order to build abstractions, you have to be very explicit.
 
== My Opposition ==
I am in the camp of those mostly opposed to generics, but more specifically I'm opposed to C++ templates.


Let's start with two basic problems. First
Let's start with two basic problems. First
Line 11: Line 24:
What will that code do? What if I give it []string{"not","what","you","expect"}.
What will that code do? What if I give it []string{"not","what","you","expect"}.


The next big problem is code explosion. If I include a line like
Also consider the error handling. If you import a package and use a function that takes an interface but fail to fulfill the interface, you get a fairly clean error message. And most importantly, it points to an error in your code, not the imported package. With templates, if you fail to fulfill the expectations of T (in the case above, having +=), you'll get an error when the package compiles.
  var foo safemap<string, bloomfilter<string> >
how much code is that going to generate? Probably quite a bit.
 
== Weak arguments ==
There are some arguments that don't hold any sway with me, lets get those out of the way.
 
=== Why do the language developers get generic and we don't ===
Because you'll abuse them, write unreadable code and then the rest of us have to clean up your mess.
 
Go works in practice because it's built on practice, not theory. It's a philosophically pure language. We get generics for slices and maps because that handles most of the cases we need. There isn't a generic bloomfilter because you don't need one very often and not everyone knows how they work.
 
=== I can't make a generic data structure ===
Yes you can, that's why we have interfaces. This is a matter of personal taste, but I've come around to liking interfaces more for this.
 
==== But I can't write a type safe generic data structure ====
Yes you can, take a non-type safe generic data structure and put a type safe wrapper around it. Yes, you will have to do it for each type, but if that really bothers you, you can write a code generator.


== Good Arguments ==
== Performance ==
One of the concerns that templates address is performance. Code using templates will generally be faster than code using interfaces.


=== Interfaces are slow ===
However, if we look at this via O notation, the story is a little different. Templates will slow down the compiler. For a fixed set of functions, interfaces will compile at O(1). Templates, using that same fixed set of functions but with N types invoked upon them will compile at O(N). And despite the fact that interfaces will be slower at runtime, they are proportionally slower, both running at O(1). That may seem like an academic argument, but there's a practical, cost consideration to it. Developer time is expensive, machine time is cheap. You'll pay that compiler cost mostly in developer and but you'll pay the running cost in machine time.
Yes, going through that pointer is a relatively expensive operation if you're doing a lot of simple ops on a lot of data. But I'm not convinced this problem justifies the inclusion of generics.


First, Go isn't **that** fast. It's fast enough for most things, but it doesn't have much in the way of optimization. If you're really worried about performance at that level, Go probably isn't the right language. And I don't think it's a good idea to fundamentally change the language to appeal to a small set of tasks the language will never be great at. At some point, I hope there will be an effort to improve the optimization and allow optimization flags that will trade compile time for run time. I think that opens the opportunity to treat interfaces like generics in some cases.
Also, Go isn't **that** fast. It's fast enough for most things, but it doesn't have much in the way of optimization. If you're really worried about performance at that level, Go probably isn't the right language. And I don't think it's a good idea to fundamentally change the language to appeal to a small set of tasks the language will never be great at.


This is also another area where code generation can pick up some of the slack.
== Type Safety ==
The strongest and most cited argument for generics is type safe containers. I certainly agree with the importance of this, but not to the extent that I would support templates.

Latest revision as of 14:05, 23 July 2017

Title: Generic: opposition and proposal

I quit Go over the lack of generics once. I learned Go for the first time in 2012. After a few weeks of playing around with the language, I decided that it was terribly designed, overly opinionated, too cumbersome and the lack of generics showed just how immature it was. And I quit. For about a year.

But I kept coming back to the few things I liked. I liked the simplicity. I liked the compilation speed. I liked the run speed (I was coming mostly from Python and PHP). And I really liked the concurrency. So after a year, I decided I'd come back, and I'd just hack my way around the shortcomings of the language. The first two things I did when I came back were figure out the reflection library and figured out how to abuse "unsafe" to get around most of the language constraints (for instance, you can gain access to and modify unexported fields with unsafe). Thus armed, I was prepared to do battle with the compiler.

Then something funny started to happen. The things I hated started to make sense. The compiler would warn me about an unused variable, so I'd go to do what I always did (some sort of no op to trick it) only to find what would have been a subtle bug or something I forgot. Maybe I accidentally used := inside an if block when I meant =. Or maybe I set an index variable in a range loop and then forgot about the code where I meant to use it. And my finely honed toolset for beating the compiler into submission sat unused. Real problem after real problem fell before maps and slices.

Then I started to really understand interfaces. I came to the language thinking "Oh, so they just all objects structs and abstract objects interfaces, only they're both stupid". But I was being stupid. It took a while, but I started to see the patterns that arise from structs and interfaces. To me, they started to seem simpler and more powerful than the patterns I new from classic OOP.

And the final nail in the coffin was when I realized that I almost never had trouble reading code other people had written in Go. And not just reading it, but being able to understand exactly how it worked. A sort of "nulles in verba" - I didn't have to trust that the code did what the semantics (variable and function names) indicated, I could easily trace through the static structures of the code and confirm for myself. Because it's hard to do "magic" in Go and in order to build abstractions, you have to be very explicit.

My Opposition

I am in the camp of those mostly opposed to generics, but more specifically I'm opposed to C++ templates.

Let's start with two basic problems. First

 func Sum(vals []<T>)<T>{
   var sum <T>
   for _,v := range vals {
     sum += v
   }
   return sum
 }

What will that code do? What if I give it []string{"not","what","you","expect"}.

Also consider the error handling. If you import a package and use a function that takes an interface but fail to fulfill the interface, you get a fairly clean error message. And most importantly, it points to an error in your code, not the imported package. With templates, if you fail to fulfill the expectations of T (in the case above, having +=), you'll get an error when the package compiles.

Performance

One of the concerns that templates address is performance. Code using templates will generally be faster than code using interfaces.

However, if we look at this via O notation, the story is a little different. Templates will slow down the compiler. For a fixed set of functions, interfaces will compile at O(1). Templates, using that same fixed set of functions but with N types invoked upon them will compile at O(N). And despite the fact that interfaces will be slower at runtime, they are proportionally slower, both running at O(1). That may seem like an academic argument, but there's a practical, cost consideration to it. Developer time is expensive, machine time is cheap. You'll pay that compiler cost mostly in developer and but you'll pay the running cost in machine time.

Also, Go isn't **that** fast. It's fast enough for most things, but it doesn't have much in the way of optimization. If you're really worried about performance at that level, Go probably isn't the right language. And I don't think it's a good idea to fundamentally change the language to appeal to a small set of tasks the language will never be great at.

Type Safety

The strongest and most cited argument for generics is type safe containers. I certainly agree with the importance of this, but not to the extent that I would support templates.