Hacker News new | past | comments | ask | show | jobs | submit login

The problem (which the article touches on) is that you have to resort to interface{} to make things reusable since Go doesn't have generics. So you have to pick between using a library (which will handle edge cases better) or compile time type checking.



You don't have to resort to an empty interface, in fact, mrust experiences go programmers cringe when they see anyone using empty interfaces. The fact of the matter that much code can reused without being "generic" and often your code never gets reused. YAGNI and all that.


The point of generic code is not purely that it's easier to re-use. The almost more important fact is that generic code has less information about its inputs and outputs—this leads to a smaller design space and, consequently, an easier time designing the implementation and an easier time avoiding bugs.


I think a lot more bugs are introduced from people trying to make their code too generic rather than too specific. The complexity of the code goes way up when you stop being able to rely on certain values being certain types. If I "hardcode" my ID to be an int64, there are a lot of assumptions that become safe to make, like the fact that it's a relatively small value, that I can bit shift it, that the keyspace is of a particular size.... if you then take the same code and decide that the ID should be genericized so it can be anything, now I have to account for the fact that the keyspace could be unbounded (as in the case of a string ID), I can't assume it's safe to copy around the value (it might not be thread safe and/or it might be a really large value).

Saying having less information about the data makes for easier coding is almost always not true.


> decide that the ID should be genericized so it can be anything

You say that, but you probably don't actually do it. If it truly could "be anything" then you would not be able to do anything to it. This is the nature of polymorphism.

So instead, your algorithm constrains the polymorphism based on what you need. If you make use of the properties {small, bit-shiftable, sized, bounded, copy-safe} then you must prove (exactly and only) that whatever the generic variable gets instantiated to satisfies {small, bit-shiftable, sized, bounded, copy-safe}.

Indeed, this is part of how such a system prevents bugs---it forces you to express exactly how much information about the types involved is needed. You can thus reflect better on the kinds of contracts/laws things must uphold and are prevented from accidentally making use of a property of your concrete type which you do not demarcate.

In a truly expressive language you might write

    foo : forall id n . 
          (Bits id, Size id = n, n <= 1024) 
          => id -> {Copy id} id
to indicate all of those properties needed (bounded being subsumed by constraining the size)

Today, you can get promises very similar to the above by using a language like Cryptol[0].

[0] https://galois.com/project/cryptol/


Ideally, the language won't permit you to make such assumptions without making them explicit. In practice, every language falls short of that, and many implementations of generics fall far short of that. Note, though, that manually implemented generics - copy-pasting and changing what you have to - isn't actually any better in this respect.


It would be really useful if Go allowed you to define methods against types imported from other packages. That way, you could define whichever interface you needed against those types (using only its public API, of course) and then use those interfaces for collections, generic functions, and the like.

The closest I've gotten to that has been to create a single-member wrapper struct. Go provides a little bit of sugar for that, but it results in a decent amount of boilerplate and explicit wrapping/unwrapping.


Simply embedding the type and writing whatever additional methods you need is actually incredibly easy. The boilerplate beyond what is required to actually define the new functions is really tiny

    type Foo struct {
        pkg.Bar
    }

    func UseIt(b pkg.Bar) error {
        return otherFunc(Foo{b})
    }
I think that's actually one of the places Go works really well. It sounds like you want something like C#'s extension methods, which I don't think are a good thing (I used them a bunch in a past job). The problem with them is that it means your code can spontaneously and mysteriously break if you move it somewhere else that isn't including the project that has the extensions. Extensions seemed nice, but they really only made the code a tiny bit cleaner, and the added complexity did not really make up for it, in my opinion.


How about replacing certain compile time type checks with smoke tests and runtime assertions, or unit tests?


Why do what the compiler can do for me?


Because you like other features of the language and you just want to get on with it.


Others just switch to a proper language instead.


Inserting the assertions by hand is annoying and error prone - even if you want the assertions to be checked at runtime its still very helpful if the programming language inserts the type checking automatically for you.

Another thing is that assertions can only check primitive type. To check if a function pointer or object respects an interface you need to add an extra wrapper around it to check all its return values (and this is so annoying to do by hand that noone bothers to do it)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: