Hacker News new | ask | show | jobs
by cageface 4260 days ago
I guess having to reimplement basic algorithms for every new container type is an example of avoiding complexity then?
6 comments

Come now -- you don't have to re-implement containers for specific type in Go any more than you do in, say, Python -- you just have to cast when you remove objects from the container.

Not great, since you're losing compile-time safety for the type -- but no worse than every single line written in a "dynamic" language like Python, and no worse than pre-generics java, say, or obj-c.

Python's dynamic typing saves you a lot over static typing without parametric polymorphism, as in Go's case.

As a concrete example, take sorting. In Go, you have to implement `sort.Interface` for each custom type you define. An example taken from the `sort` package:

    package main

    import (
        "fmt"
        "sort"
    )

    type Person struct {
        Name string
        Age  int
    }

    func (p Person) String() string {
        return fmt.Sprintf("%s: %d", p.Name, p.Age)
    }

    // ByAge implements sort.Interface for []Person based on
    // the Age field.
    type ByAge []Person

    func (a ByAge) Len() int           { return len(a) }
    func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
    func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

    func main() {
        people := []Person{
            {"Bob", 31},
            {"John", 42},
            {"Michael", 17},
            {"Jenny", 26},
        }

        fmt.Println(people)
        sort.Sort(ByAge(people))
        fmt.Println(people)

    }
An equivalent in Python is quite a bit simpler:

    class Person(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age

        def __repr__(self):
            return "%s: %s" % (self.name, self.age)

    people = [
        Person("Bob", 31),
        Person("John", 42),
        Person("Michael", 17),
        Person("Jenny", 26),
    ]

    print people
    people.sort(key=lambda person: person.name)
    print people
If it were just sorting, it wouldn't be that big of a deal. But there's a lot of nice abstractions missing from Go due to the lack of generics - e.g. higher-order functions like `map`.
Your examples aren't quite equivalent -- `people` in Go refers to a contiguous chunk in memory of size len(people) x sizeof(People). Thus, in the Sort() method, there needs to be something that binds the sort-algorithms need to swap elements to the specific code to swap that many bytes. Go does this, at the cost of some boilerplate, with the ByAge interface. (And other languages have more elegant ways to do this.)

But we are doing something you can't do at all in Python (barring specialized array modules or similar).

The equivalent of the Python code would be something like:

    package main

    import (
        "fmt"
        "sort"
    )

    type Person struct {
        Name string
        Age  int
    }

    func (p Person) String() string {
        return fmt.Sprintf("%s: %d", p.Name, p.Age)
    }

    func main() {
        people := []interface{}{
            Person{"Bob", 31},
            Person{"John", 42},
            Person{"Michael", 17},
            Person{"Jenny", 26},
        }

        fmt.Println(people)
        SortBy(people, func(a, b interface{}) bool {
            return a.(Person).Age < b.(Person).Age
        })
        fmt.Println(people)

    }
where we have a library function

    // SortBy can sort anything -- just give a comparison function.
    func SortBy(elts []interface{}, less func(a, b interface{}) bool) {
        sort.Sort(byFunc{elts, less})
    }

    type byFunc struct {
        elts []interface{}
        less func(a, b interface{}) bool
    }

    func (a byFunc) Len() int           { return len(a.elts) }
    func (a byFunc) Swap(i, j int)      { a.elts[i], a.elts[j] = a.elts[j], a.elts[i] }
    func (a byFunc) Less(i, j int) bool { return a.less(a.elts[i], a.elts[j]) }
This is exactly the same, expect for instead of saying "Give me .age" like we do in Python, we have to explicitly cast and say "I'm expecting a person, give me .Age".
> Your examples aren't quite equivalent -- `people` in Go refers to a contiguous chunk in memory of size len(people) x sizeof(People).

How it's laid out in memory is tangential to the original critique - namely, that Go introduces complexity by not having parametric polymorphism.

> But we are doing something you can't do at all in Python (barring specialized array modules or similar).

Python not only allows this (I don't see why it wouldn't), it has built-in support for an equivalent for sorting via rich comparison methods [1]. The point is, though, that you don't have to.

As for the Go example, it's not equivalent because you're writing your own boilerplate code. And it's throwing static typing out the window, which doesn't seem very idiomatic.

1: https://wiki.python.org/moin/HowTo/Sorting#Odd_and_Ends

Hmm? Sorry, I wasn't trying to say Python doesn't let you define comparison functions, I was just reiterating that it doesn't let you sort an array of embedded objects, as opposed to pointers.

This is not a tangential difference. It's a real, important language decision, with benefits and consequences.

If you restrict yourself to arrays of interface{}, like python, then you can do exactly what python does, at the cost of losing compile-time type checking...exactly like python :)

I believe my example is equivalent to the python code; it doesn't contain any boilerplate (which is something you have to write over and over again), but rather a short library function, SortBy, which you would only have to write once.

(The normal Go sorting routines do contain boilerplate, since the Swap and Len methods are basically copy-paste jobs.)

That said, I agree using interface{} everywhere is not idiomatic Go, and certainly using things like map and fold in Go would be so awkward as not to not be worth it.

But doing something like writing a heap class that required casting when removing the object would be pretty reasonable, IMO.

You're not throwing static typing out the window, you're just losing it in that isolated case. It's more like...pushing static typing slightly out of the comfy spot on the couch. That's unfortunate!...but maybe not that bad -- after all, dynamic languages get along alright w/o static checks anywhere. :)

But do those library functions actually exist or did you write them just now?
I wrote SortBy, but it was trivial, and you'd only have to do it once.

(The bigger issue is probably in Go you'd rather just have the boiler-plate than using []interface{}. But that's what you're essentially doing in a dynamic language like Python, and you can do it in Go, too, if you want.)

> and no worse than pre-generics java

Personally, that was the turning point when Java became palatable. Which is a shame, because Go is all-around a pretty nice language. But writing real, production code, I've felt that I was needlessly repeating myself because of the lack of generics. Specifically, I really wish I could write generics over channels, so I can solve the problems of multiplexing/demultiplexing channels, throttling them, etc exactly once.

(It's worth pointing out that Go does offer lots that pre-generics java doesn't, in particular interfaces w/ structural typing.)

Agreed that not having generics is a real pain-point, though not as bad as I would've assumed before using the language.

For the channel stuff, you could always just make use "chan interface{}", have your generic chan library, and then cast when receiving from the channel.

Or you could use code generation to make specific versions of your library for various types, maybe using the upcoming go generate.

It's kind of interesting, because C++ templates, for example, essentially are code-generation, but just hidden away, and for that reason tend to get overused (IMO), resulting in crazy-long compile times.

If Go's solution to generics ends up being "generate the code explicitly", it would seem uniquely Go-ish -- inelegant, but might work well practice.

(I do sort of dream about some kind of generics in Go -- something like

  where X interface{}
  type LinkedList X struct {
    Obj X
    Next *LinkedList X
  }
and maybe...

  where X Lessable
  type Heap X []X

  func (h *Heap X) Push(x X) {
     ...
  }
or even just

   where X interface{}
   func PushHeap(h []X, x X, less func (a,b X) bool) {
      ...
   }
Ah, to dream :)
> no worse than pre-generics java

exactly.

> obj-c

Objective-C is essentially dynamically typed.

Truth, but it does let you write down types for method arguments and instance variables, and the compiler will warn(!) you if you violate those -- if you're sane, you'll treat those warnings as errors.

So it's got a static typing feel, even if it is all just dynamic method lookup at runtime.

>but no worse than every single line written in a "dynamic" language like Python

Yes, much worse.

>and no worse than pre-generics java

That is setting the bar so low it is underground. I don't want "no worse than a language that was born obsolete 20 years ago", I want "good".

I write Go all day every day and have since before Go 1.0, and I see this troll/complaint at least 100x on hacker news for each time I have to do it once in real life.

On the rare event this actually matters I use templating tools if performance is needed, but if not (which is usually the case) I use an interface. But like I said, this is so rare.

P.S. I write distributed databases / big data engines and distributed content optimizing proxy servers.

> I write Go all day every day and have since before Go 1.0, and I see this troll/complaint at least 100x on hacker news for each time I have to do it once in real life

I suspect that there is as much trolling in people who complain about generics, as there is survivor bias in those who reply they haven't needed them in X years of go. If you felt that you needed them, you wouldn't have lasted long using go!

Those are fair points. I also think major part of the issue here is all the folks that try to write Java/C++/Python,etc in Go, rather than Go in Go. People install Go, write something in Go, paying no mind to go idioms, and complain. It happens on the Go mailing list all the time.
I truly don't see many people talking about leaving go after getting frustrated with no generics. If this were survivor bias, where are the casualties?
I assume most people who complain about Go's lack of generics have at least tried the language. It doesn't take long to run into your first interface{} and subsequent typecast. So, if you define "leaving Go" as "not switching to it", there are many. :)
It seems like quite a leap of faith to assume a significant portion of people complaining about something on the internet have actually tried it. Most complaints about Go tend to show the opposite. For example "it doesn't take long to run into your first interface{}"... what? Sure it does. If you're attempting to write Go like Go (and not trying to write Go like Java/C#/Haskell/whatever) then you can code for a very long time without hitting your first interface{}.
Don't get me wrong, I'm not saying that interface{} is a terrible thing. I spent a decade writing assembly language and then just as much in C and C++ before templates were usable, I know generics are far from essential and that a little controlled type unsafety is not a huge problem in practice. But:

> you can code for a very long time without hitting your first interface{}

Negroni, a popular and self-appointedly idiomatic library: https://github.com/codegangsta/negroni/blob/master/negroni_t...

    func expect(t *testing.T, a interface{}, b interface{})
    func refute(t *testing.T, a interface{}, b interface{})
Ok, that's just for testing - it's not unusual to have to go off path to be able to test your code. How about Google's own standardized idiom for production code? https://blog.golang.org/context

    Value(key interface{}) interface{}
Or Go's own standard library? http://golang.org/pkg/encoding/json/

    func Marshal(v interface{}) ([]byte, error)
    func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
    func Unmarshal(data []byte, v interface{}) error
No, it does not take such a long time.
From the article:

> Go isn’t perfect for every task. [...] Ultimately, it is up to you and your team to decide the best language and tools with which to build your application. While making that decision, I hope that you will take a moment to weigh the trade-offs that come with your choices.

It's a tradeoff based on your real, hard needs. If you decide that only having Array<T> and Map<T> is a deal breaker, and the complexity of creating a tool to autogenerate the go code for a large number of SinglyLinkedList<T>/DoublyLinkedList<T>/CircularLinkedList<T>/etc... outweighs the uses of Go, then don't use Go.

If you are fine writing a quick dirty code generator that can look for some <T>-like syntax and do simple replacement, or only really need Array<T> and Map<T>, then consider Go.

It's not like the language is forcing you to use it against your will.

> If you decide that only having Array<T> and Map<T> is a deal breaker, and the complexity of creating a tool to autogenerate the go code for a large number of SinglyLinkedList<T>/DoublyLinkedList<T>/CircularLinkedList<T>/etc... outweighs the uses of Go, then don't use Go.

With all due respect, but how is having to build a custom code-generator first, before you can write your actual program not accidental complexity?

I wasn't claiming it isn't accidental complexity. Just pointing out the author essentially says to weigh the decision appropriately.

Looking at the sibling comment about "go generate" by buro9, it looks like a custom code-generator won't be needed either.

EDIT: Added buro9, and emphasis

Not yet, anyway.
go generate is in 1.4 which will arrive shortly

The proposal (the implementation is pretty much this) is here: https://docs.google.com/a/golang.org/document/d/1V03LUfjSADD...

If you feel a real strong need for generics, for something like a "sort by property of my custom type... do it for n types", then it's now trivial to generate that.

You can code like you have the features the core doesn't support, whilst having the succinctness, performance and maintainability that the language delivers.

I've coded Go almost daily for 2+ years and only a couple of times had the urge/need for generics... I dunno, maybe I'm the exception here.

I've had the same experience - very little need for generics in Go on a regular basis. I really appreciated the following snippet from http://robnapier.net/go-is-a-shop-built-jig:

"Probably the biggest complaint people have with Go is the lack of generics. And I did run into that in just the first couple of weeks of work on my project, and I wound up with a bunch of duplicated code to work around it. And then, when it was all working, I refactored out the duplicated code. And I refactored again. And in the end, the whole thing was simpler and shorter than what I would have done with generics. So again, in the end, Go turned out to be a language for solving real problems rather than a language filled with beautiful tools, and so you build real solutions rather than finding excuses to use your beautiful tools. Don’t try to make Go what it isn’t. If you’re trying to solve abstract CS problems in their most generalized forms, then you may find it frustrating. But if you’re trying to solve specific, practical problems in the forms professional developers typically encounter, Go is quite nice."

So ..... he ended up doing far more work than he would have done had Go been like any other modern language? Without detail about what these refactorings were and why he didn't write the code that way at first, what lessons he drew from it etc this anecdote is not very useful.
> eg bindata: translating binary files such as JPEGs into byte arrays in Go source

I'm doing that myself in a terribly hacky way using custom shell scripts and a bit of python to print out a .go file -- but seeing it in this list makes me wonder if it is a common problem that has been solved better already - does anyone know of any tools for this? (I imagine something like "bin2src -i foo.jpg -o foo.go", with support for a variety of languages)

I guess real-world softwares don't need fancy containers other than a list and a map, and when they actually really do need them the copy-paste turns out to not be that much of a burden ?
People need to start downvoting the "Go doesn't have generics" trolls. This has been addressed many times. If it really is a showstopper for you, that's fine. There's no value in trolling Go submissions.
Is there some rule that only people who are done talking about generics may participate in conversations about Go? I was happy to see this discussion here.
It's your "Ground Hog Day". I'm not sure how much more there is to get out of the comment: "Go doesn't have generics, doesn't work for me."