4 ms·
> 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 mos
by wwalexander 5y ago
> 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 https://en.m.wikipedia.org/wiki/Mars_Climate_Orbiter
- naniwaduni 5y ago> 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.
- wwalexander 5y ago“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.
- deleted 5y ago[deleted]
- dnautics 5y agoIt'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.
- wwalexander 5y ago> 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&edition=2018&gist=cfae04434e74615018a29338f5eb78c2 https://play.rust-lang.org/?version=stable&mode=debug&editio... > cannot multiply `i32` by `f32`