Hacker News new | ask | show | jobs
by zeekay 4118 days ago
I've also found the lack of parametric polymorphism a huge pain point. Type safety is constantly sacrificed to allow for code-reuse and nicer APIs, leading to really awful code using type switching at best, inscrutable amounts of reflection at worst. This seems to plague the Google developers as well, just look at the Go App Engine APIs.
1 comments

I built ported part of a distributed system I have in production to Go about 2 years ago. I remember doing a lot of reflection trying to factor out some common SQL code. Like this:

    func CreateRecord(record interface{}) (err error) {
        t := reflect.TypeOf(record)
        v := reflect.ValueOf(record)
Where I named the struct the same thing as the table it mapped to.
There are often alternatives. One of the ones I picked up from Haskell (of all places) is having an instance of something in hand solely for its type. I don't know what all you were about to do with that record, but in this case, you may have been able to use:

    type Record interface {
        Create() Record
    }
which declares an interface of things that know how to create another copy of themselves and return it. Well, the type doesn't guarantee that the same type will come out but you can document that. Then you don't need reflection to create a new instance, as long as you can get an instance of the correct type from somewhere else. You presumably have some arguments that goes in, but it's reasonably likely (though not certain) that there's some sort of regularity you can exploit and put into the type signature of Create() up there.

I've used this pattern myself in a generic "game server" that implements a network protocol and manages creating "rounds" of games and other high-level bookkeeping, where you pass it in a "prototypical" game object that provides a "CreateNew()" method on it, thus making it so the core engine can create new games without ever actually knowing what the game itself is.

If it's good enough for Haskell it's good enough for Go.

(Haskell, a bit confusingly, calls this a Proxy, perhaps because the instance is standing in as a proxy for the type? I was never quite sure where the name came from.)

I've actually written quite a bit of Go now without needing reflection, and the only "interface{}"s in the system are either A: things that legitimately can be "anything" (on a per-instance basis, i.e., not ATDs that really want to just have one type in them but legitimately on an object-by-object basis may contain an "anything") or B: things I really want to say are "encodable/decodable as JSON by the standard encoding/json library but there's no clear way to say that in the type". The latter annoys me in theory a great deal more than it annoys me in practice.

Mind you, I acknowledge there's a point where you'll be left with no other options but an interface{} or reflection, but people do end up reaching for it more quickly than is strictly speaking necessary. And it isn't necessarily a compliment for Go that it makes you think a bit harder for this sort of thing.