6 ms·
I’m not a Go developer, but I understand that from a type and mathematical theory perspective Go’s time.Duration is extraordinarily awful, because of Go’s simpl
by chrismorgan 5y ago
I’m not a Go developer, but I understand that from a type and mathematical theory perspective Go’s time.Duration is extraordinarily awful, because of Go’s simplistic type system.
int64 * Duration → Duration and Duration * int64 → Duration both make sense, but I gather this only works with constants. For other values, I believe Go only gives you Duration * Duration → Duration which is just wrong, wrong, wrong, requiring that one of the two “durations” actually be treated as though unitless, despite being declared as nanoseconds.
In the end, it’s probably still worth it, but it’s a case of Go trying to design in a certain way for ergonomics despite lacking the type system required to do it properly. I have found this to be a very common theme in Go. Also that it’s often still worth it, for they’ve generally chosen their compromises quite well. But I personally don’t like Go very much.
- svnpenn 5y agoAll that, and yet I don't see a single example of what would be "correct", or an example language that does it better. This just comes off as poorly thought out rant. In my opinion, it is well designed. First of all, who is multiplying time.Duration against itself? I've been programming Go for a few years basically every day, and I've only ever seen the package constants used by themselves, or with untyped constant. I think it's a great syntax, better than any example in the article, as you don't have mystery numbers.
- manwe150 5y agoJust contrived examples, like gravity * t^2 to get distance to fall and such, probably
- bobbylarrybobby 5y agoWhat is correct is that duration ± duration = duration, duration * scalar = duration, timestamp ± duration = timestamp, timestamp - timestamp = duration, and anything else doesn't compile.
- Groxx 5y agoTo call it out specifically: this does not include `duration * duration = duration` Go is currently allowing that, which makes `delaySecs * time.Second` a billion times larger than it appears to intend. I've personally run across code that has this kind of flaw in it... at least several dozen times. It's the kind of thing that's only noticed when it misbehaves visibly while someone is watching it. (I read a lot of other-teams' code, which is in various states of quality and disarray)
- chrismorgan 5y agoAnd it’s not just that Go allows that, but that that’s actually the only general way of doing it, as Go only allows duration * scalar in constant context (which is admittedly all most people do with durations, which is why I say it’s still probably better than Go did it this way, given their deliberately limited type system).
- svnpenn 5y agoIf you exponent time.Duration, that's a user error. What you're suggesting would be like a compiler error for multiplying two int64s
- conradludgate 5y agoIt should be fine to multiply two durations, but it should not return the same time.Duration type
- linkdd 5y agoWhat they're suggesting is that `time.Duration` should not be an int64. 1 second * 1 second = 1 second² 1 meter * 1 meter = 1 meter² 1 meter / 1 second = 1 m/s Those are not user errors, those are physical values. A physical value is two things: - a scalar (int64, float, ...) - a unit (meter, second, inches, ...) If the type system of your programming language does not allow you to define units, this should at least be a structure with a scalar and an enum, and functions to cast from one unit to another (if possible). Working with units is a common thing in science.
- thwarted 5y agoThe provided example is pretty rough, and I'm sure it's occurred in the wild. Sure, put the units in the variable name, and it encourages this kind of mistake, because time.Duration is not "seconds", it is a duration. The variable name should match the API of time.Sleep, which takes a duration. The variable should be named delay. A variable named delaySecs is the same kind of maintenance headache as a variable named "two_days_ago = 2.days.ago" in ruby. The variables used for accepting and parsing input are the ones that should have units in them in this example. Although if you need a delay specified, it's valuable to be explicit and robust in the input and accept a string time.ParseDuration understands. Then you don't have this units problem in your variable naming at all, allows easier input of wider ranges of values by the operator, and makes input validation (if only a subset of durations are allowed) more concise and consistent.
- conradludgate 5y agoI've seen a lot of code at my last job in Go where all duration variables included their units. It was amazingly bad Go code (it was built in part of the projects PoC by new Go devs) but it doesn't help that for the most part it did work. Time was honestly our biggest source of bugs by far. Although adding time.Time ended up being more problematic than durations which were mostly only constructed like that in tests
- tedunangst 5y agoOne alternative is rust Duration, which makes you spell out all your arithmetic operations. But this is exactly the approach taken by go time.Time, so the part about the type system being incapable or whatever is kinda misguided. https://doc.rust-lang.org/std/time/struct.Duration.html https://doc.rust-lang.org/std/time/struct.Duration.html C++ chrono::duration allows arithmetic operations with more sensible overloading. https://en.cppreference.com/w/cpp/chrono/duration https://en.cppreference.com/w/cpp/chrono/duration
- chrismorgan 5y agoOnce you go beyond constants (which in practice means literals, I think, but I’m not conversant enough in Go to be confident), Go requires that you multiply Duration by Duration—you can’t multiply it with a scalar outside of constants. In other words, as soon as a scalar multiple becomes a parameter rather than a constant, you can’t just do `n * duration`, but have to do something more like `Duration(n) * duration`, which is obviously physically wrong for a system of units, because the unit should be time squared, not time. As for languages doing it better, approximately every single language that has strong static typing and uses dedicated types for times does it better. Rust is the one I’m most familiar with and comfortable with.
- svnpenn 5y agoYou seem to be confusing the Duration type with Duration values. Yes, if you insist on using a typed value, instead of an untyped constant, then that value needs to be type Duration. But Duration(1) is way different than time.Second. honestly it just sounds like you don't know the language, and aren't willing to learn it. Go is not Rust. Things are different, that doesn't mean Go sucks.
- chrismorgan 5y agoThe entire purpose of the distinction that I’m remarking on is that Duration * Duration → Duration is mathematically utterly incorrect, and especially super misleading when the base quantity for the unit is nanoseconds rather than seconds, yet that is what Go requires, beyond constants, which it special-cases. To be sure, with durations, the distinction doesn’t matter much because arithmetic performed is with constants, and that’s why I say that Go is probably still better with this wonky unit scheme than with entirely unitless quantities, but there are plenty of situations where you will want to multiply durations by typed numbers, and so you’re forced to do the mathematically-ridiculous `Duration(n) * duration` rather than `n * duration`.
- svnpenn 5y agoI'm sorry, but I just don't think people use code like you're describing. In your mind, you see this: hello := 2 hello * time.Second // oh no But people actually use code like this: hello := time.Second hello *=2 People working with Go code (who know what they're doing), don't declare an int, only to immediately cast it to something else.
- xigoi 5y agoNim does it correctly: https://nim-lang.org/docs/times.html#%2A%2CDuration%2Cint64 https://nim-lang.org/docs/times.html#%2A%2CDuration%2Cint64
- int_19h 5y ago> All that, and yet I don't see a single example of what would be "correct", or an example language that does it better. F#, Rust, C++?.. > First of all, who is multiplying time.Duration against itself Physicists do, every time they have to deal with acceleration - m/s^2.
- WinterMount223 5y agoYou are right however physicists do not multiply duration x duration but time x time. Duration is discrete and time is infinitesimal.
- crdrost 5y agoI mean in their defense I see almost nobody doing this right... Like to do full SI you really need 7 rational numbers, maybe a sort of inline “NaN” equivalent for when you add things with incommensurate units... you might also want display hints for preferred SI prefixes (might just be a dedicated format type), and while you're at it you might as well use a Decimal type instead of doubles, oh and probably these numbers should have an uncertainty in them, so probably you want a modular system where you can mixin units or mixin uncertainty and you can start from a base of doubles or decimals or, hell, you start experimenting with continued fractions...Sigh. The real world is complicated. Every implementation of units is secretly trying to be Moment.js, basically.
- wnoise 5y agoAnd yet people actually do manage meaningful unit systems that don't allow this.
- tfigment 5y agoCompany i worked for 20 years ago had commercial engineering modelling program that did all physical types correctly and did scaling for user but it was fairly unique in scope and like you say almost all programming languages fall short here and it had its own quirks.
- int_19h 5y agoAll you need is a language that actually incorporates units of measure into the type system - i.e. you can define units orthogonal to types (including relationships between units), and then you can specify both the unit and the underlying numeric type in a declaration. https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/units-of-measure https://docs.microsoft.com/en-us/dotnet/fsharp/language-refe... This can also be pulled off with somewhat less pleasant syntax on top of a sufficiently flexible parametrized type system - e.g. C++ templates.
- account42 5y agoC++11's std::duration manages just fine without that much scope bloat. operator* for two durations is simpley not defined so will lead to a compile error.
- maerF0x0 5y agomy 2c, I think the issue is simply in the name collision of units and the constants. (ie, "seconds" and time.Seconds) . In reality most programming languages do not have units what so ever (built into the language, maybe tacked on as a library after the fact). They have int64s, a unitless value that just keeps track of whole numbers of whatever it semantically means to the developer. If we want to truly have units in values then we either need 1st class language support (including syntactical support) or a rich library that doesnt just "put units in [symbols]". One could probably make it happen with a type that keeps track of units ``` type unitedVal struct { denomerator int64 numerator int64 denomUnits string numerUnits string } func (united *unitedVal) Multiply(by unitedVal) *unitedVal { return &unitedVal{ numerator: united.numerator*by.numerator, denominator: united.denominator*by.denominator, denomUnits: united.denomUnits + " * " + by.denomUnits, numerUnits: united.numerUnits + " * " + by.numerUnits, } } ``` and then filling out for all the other operations you want to support.