Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Well, no language ever needs features. We could all write software in C or even assembly, but we don’t because abstractions are nice. Generics are an abstraction.

Take C# for example. The `System.Collections.Generic` namespace is full of generic collections (surprise!) that allow more type safe code. If I have a `List`, I can’t guarantee there isn’t something I don’t want in there (that could cause a runtime exception I don’t catch). But if I have a `List<IFeature>`, I know that everything in the list implements `IFeature` (barring compiler bugs and unsafe code).



To be fair, Go has typed maps and slices. You just can’t implement your own generic collections, or generic operations over collections. Those two will get you decently far.


But that’s all go has. It doesn’t have typed Sets, typed ordered maps, etc.


I don't know how people were looking at these Java 2.0-esque kludges and still arguing generics were superfluous but I'm glad it's landed: https://github.com/elliotchance/pie


Abstractions are not always nice.

One of the nice things about go is it doesn’t have many abstractions and most of them are carefully thought out. I don’t want to have to inhabit somebody else’s abstractions all day at work, I want the language to get out of the way, which go does quite well IMO.


"and most of them are carefully thought out."

Is this stockholm syndrome or something? For example, JSON.

Also, the time abstraction is completely bonkers.

If you want to multiply a 500 milliseconds with a user-given value (suppose I want some number of half-seconds), you must first cast the user value to milliseconds, then multiply, so you are multiplying 500 milliseconds times (say, 4 milliseconds) to obtain 2000 milliseconds.

And don't get me started on goroutines/channels. You are supposed to "share state by communicating" and not "communicate by sharing state". That's great. But at some level, you must bootstrap knowledge about the state of the channel, which is fundamentally shared state by the low-level nature of the channel. When you've worked in systems which have really thought out carefully what it means to have a no-shared-state system, the go system looks like it's been put together by either amateurs or fools.


There’s certainly lots to criticise in Go, as in any language, that’s why I said ‘most’, though I’d say it was peak HN hubris to say it was put together by ‘amateurs or fools’.

JSON is not related to go but perhaps you have some problem with the JSON parser? Works fine me… Personally I don’t find the time constants a problem at all, I’m not keen on the time parsing layout, but that’s relatively minor. Channels I don’t have strong opinions about and goroutines if used sparingly I’ve found a nice balance of utility and simplicity for creating threads, but I recognise I’m not really qualified to argue about them.


It's not hubris, if you've seen something else to compare it to. I'm not claiming to have made something better, I'm claiming to have used more well thought out systems. I know Rob pike isn't an "amateur" in the strictest sense (he might be, at "designing a pl"). I'm not convinced he isn't a fool.


There seem to be a number of people defending the need to convert both multiplicands to milliseconds. Are people overlooking the fact that if you multiply two values with unit ms, your result has a unit of ms^2? The fact that you get ms out of the multiplication in Go is stupid and incorrect.


> you must first cast the user value to milliseconds

Incorrect. You must cast the user value to a time.Duration, since Go is a type-safe language, and like most type-safe [1] languages, its numerical operations generally require their operands to be of the same type.

You might be thinking of multiplying a user-provided value by a constant (of type time.Duration) defined by the time package, like time.Millisecond or time.Hour. I’m under the impression this is a very intentional choice to require user-provided values to be explicitly annotated with their units. Some implementations/variants of durations use nanoseconds, some milliseconds, some seconds, and requiring this assumption to be explicit in the code helps avoid critical bugs like the Mars Climate Orbiter failure. [2]

The time package (and other stdlib packages) definitely has some warts, especially the time format parsing, but I’ve always appreciated the approach taken for durations.

[1] We could get into more advanced type inference here like Rust’s From/TryFrom traits, but the debate between simplicity vs. expressiveness in Go has been retreaded here tens of thousands of times and I doubt either of us has anything new to say on the topic.

[2] https://en.m.wikipedia.org/wiki/Mars_Climate_Orbiter


> Incorrect. You must cast the user value to a time.Duration, since Go is a type-safe language, and like most type-safe [1] languages, its numerical operations generally require their operands to be of the same type.

This is cargo-cult type-safety; stop a moment to consider the dimensions implied by the types.


“Units” in the last bit was maybe the wrong word.

To be clear: time.Duration is a type, and is the only part of this discussion where “type safety” is a factor.

time.Millisecond is a constant time.Duration whose value represents the duration of a millisecond. time.Millisecond et al are not a unit as in chemistry class, so you’re not supposed to get a time.Millisecond^2 by multiplying them. Just like in any other typed language, T * T -> T, not T^2 (where T = time.Duration), since it’s a type and not a unit.

time.Millisecond et al instead make you explicitly annotate the scale of user-provided durations. When you write

    d := time.Second*time.Duration(input)
you are assigning to d a value of type time.Duration equal to the duration of one second multiplied by the input. You are free to not use the constants defined by the time package and instead create time.Durations from the literal number of nanoseconds in the duration, if you so choose:

    d := time.Duration(input)*1000000000
But since the time package already defines convenient constants for standard durations, those will be used instead and be far more explicit and readable.

This has nothing to do with dimensional analysis of units, of the sort you do in physical sciences. I don’t know of any general-purpose languages that include dimensional analysis in the language or their standard library’s datetime package, but I’d be curious if you know of one (that isn’t specifically geared towards the sciences) - seems like the sort of thing Ada might include? In any case, I don’t think a language focused on keeping a simple feature set like Go should be a trailblazer here. Durations are easy.


It's still bonkers to make you cast an non-duration input to time.Duration. this causes cognitive confusion, because you are effectively labelling the multiplicand as something that it isn't. All pls with types have semantic meaning for the types if nothing else, so if you can't see why this is a real problem, I can only say: Stockholm syndrome.

"numerical operations generally require their operands to be of the same type"

The correct decision would have been to make the * operator be allowed to operate on a time.Duration and an integer, just as you are uncontroversially allowed to operate * on a float and an integer -- to refute your statement and go even stronger I don't know of ANY pls that require * operands be the same type.

However, that is not what go chose. And we are talking about the choices go made. This is very much NOT well thought out and very ill-considered, especially since "the right thing" is so easy.


> I don't know of ANY pls that require * operands be the same type.

Haskell is an example, where the type of * is:

    (*) :: Num a => a -> a -> a
Which says that the arguments must be numeric, and of the same type. I think this is the sensible choice from a strongly-typed perspective, and some operation which allows one to multiply a time value should be a separate thing.


But you can define your own * operator, separate to the one from Num, with any types you like, can't you? You might have to hide the one from the prelude to use it.


> to refute your statement and go even stronger I don't know of ANY pls that require * operands be the same type.

This really comes down to a lack of experience on your part. Haskell requires both arguments to be of the same type and only in the case where it can reasonably infer from a literal that it could be coerced will it do so. OCaml, as another example, requires an entirely different multiplication operator for floats.

Requiring the same type for both arguments is not as rare a position as you've made it out to be, and not a showstopper in any case either.


> this causes cognitive confusion, because you are effectively labelling the multiplicand as something that it isn't

You make a fair point, but at that point the debate is about other choices made in the Go language disallowing implicit type conversions.

> The correct decision would have been to make the * operator be allowed to operate on a time.Duration and an integer, just as you are uncontroversially allowed to operate * on a float and an integer

What should the resulting type of the multiplication operator be when applied to a float and an integer? Should the type be different if the operands are swapped? Is it acceptable for the multiplication operator to not be commutative, given that we seem to be demanding a great deal of rigor from our type system? Should we only allow this implicit conversion if type inference is not being used?

> To refute your statement and go even stronger I don't know of ANY pls that require * operands be the same type

Rust, for one, but will test out some more when I’m not on a bus: https://play.rust-lang.org/?version=stable&mode=debug&editio...

> cannot multiply `i32` by `f32`


> Rust, for one, but will test out some more when I’m not on a bus

You can impl Mul for your own types. The operands' types don't need to match.


When I don’t like how something was done in a Go codebase, I’m generally SOL, since it was repeated so many times that there aren’t enough hours in the day to change them all.

When extremely motivated, I’ll script manipulation of the AST, but that’s a pretty extreme thing to have to do.


Scripting manipulation of the AST is something that tooling can make much nicer. That might not exist (yet) for Go, but I think there is a lot of value in making the language easy to understand for tooling. Compare for example the quality of IDEs for Java vs C++.

But this is of course orthogonal to generics, as you can make generics friendly to tooling as well, see Java.


Abstractions add a great deal of "default", potentially very complex, behaviour. This is great when starting a greenfield application. Even in extreme cases like RoR they work and quickly get a whole lot of things running. Fantastic. My newfangled thingamabob does something!

And then the application grew and you have the guys who have to watch and keep the application online when it's business critical. They HATE abstractions. Because 7 abstractions that are used throughout the application means there's 127 cases the developer hasn't thought through, at least ten of them a ticking time bomb. That's 128 possible cases, one of which the developer has actually thought about, 5 they have verified to be reasonable.

An easy case to show what's happening is the suggestion every new developer makes. "I'll just have a thread per connection, that's easy". And yes, it's very easy to get it running. It doesn't block during dev, it handles multiple connections and generally does the job. And it's absolutely guaranteed to crash you server for 10 different reasons in production. And yet, every new developer will (and should) do it.

There's just 2 camps developers in the world, who don't agree and this won't seriously change. Learn how the "other camp" thinks and you'll do better.


I think you're confusing abstraction for framework.


Go, like every language, has plenty of footguns and head-scratching behaviors as well. Don't confuse your familiarity and personal preferences for universal properties of the language.




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

Search: