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

I think Go has a reasonable approach:

  time.Sleep(1 * time.Second)


Like many things with Go, its approach seems reasonable and simple at first, but allows you to accidentally write code that looks right but is very, very wrong. For example, what do you think this code will do?

    delaySecs := 1 * time.Second
    time.Sleep(delaySecs * time.Second)
Now I insist on using the durationcheck lint to guard against this (https://github.com/charithe/durationcheck). It found a flaw in some exponential-backoff code I had refactored but couldn’t easily fully test that looked right but was wrong, and now I don’t think Go’s approach is reasonable anymore.


Perhaps the function shouldn't accept the unit of sec². Not least because I have no idea what a delay in that unit could signify.


It doesn't actually use units. Everything is in nanoseconds, so time.Second is just another unitless number.

  const (
   Nanosecond  Duration = 1
   Microsecond          = 1000 * Nanosecond
   Millisecond          = 1000 * Microsecond
   Second               = 1000 * Millisecond
   Minute               = 60 * Second
   Hour                 = 60 * Minute
  )


Note that the wonderful Go type system interprets time.Second * time.Second as 277777h46m40s with the type time.Second (not sec^2)


  time.Second * time.Second
The type of this is `time.Duration` (or int64 internally), not `time.Second` (which is a const with a value).

I agree, though, that this is not quite sound, because it can be misused, as shown above with `time.Sleep(delaySecs * time.Second)`.

In Kotlin you can do `1.seconds + 1.minutes` but not `1.seconds * 1.minutes` (compilation error), which I quite like. Here is a playground link: https://pl.kotl.in/YZLu97AY8


Certainly, but for that the type system should be rich enough to support unit designators.

I know how to implement that in Haskell, and that it can be implemented in C++ and Rust. I know how to logically implement that in Java or Typescript, but usability will suck (no infix operators).


Go tends to cover such things by incorporating them directly in the language. But then it tends to not cover them at all because it would "overcomplicate" the language...

For a good example of what it looks like when somebody does bother to do it, see F# units of measure.


This looks to me like the semantics are good but the implementation details are broken. 1 * time.Second * time.Second semantically reads to me as 1 second. If time.Second is some numeric value, that’s obviously wrong everwhere unless the type system reflects and enforces the unit conversion.


> 1 * time.Second * time.Second semantically reads to me as 1 second.

Which is wrong, 1s * 1s = 1s².

For example, the force of gravity is expressed in m/s² and describe an acceleration (m/s / s, aka a change of velocity per time units, where velocity is a change of distance per time units).


Okay so do I need to consult Relativity to program 1sec + 2min?


Since 1min could be 61 seconds[1], yes?

But assuming your comment is not a joke. You probably want to convert minutes to seconds in order to work with the same units, then add the scalar parts together.

That's how you deal with different quantities: convert to same unit, add values.

This is analog to fractions: 1/2 + 1/4 = 2/4 + 1/4 = (2+1)/4 = 3/4.

  [1] - https://en.wikipedia.org/wiki/Leap_second


In basic middle school math it’s common to multiply different units as a basic conversion mechanism. Multiplying by the same unit is semantically equivalent to “x times 1 is identity(x)”, and other cross-unit arithmetic implies conversion to ensure like units before processing. A typed unit numeric system would imply that to me. It would not imply I’m multiplying the units, but rather the scalar value of the unit.


> In basic middle school math it’s common to multiply different units as a basic conversion mechanism

EDIT: Yes, you multiply the units `2m * 2s` : you first multiply the units to get: `m.s`. This is what I say: you convert everything to the same units before doing the calculations.

> Multiplying by the same unit is semantically equivalent to “x times 1 is identity(x)”

This is wrong.

1kg * 1kg = 1kg² period.

What you're saying is `2kg * 1 = 2kg`, which is right, because `1` is a scalar while `2kg` is a quantity. This is completely different than multiplying 2 quantities.

> It would not imply I’m multiplying the units, but rather the scalar value of the unit.

That's where you're wrong. When doing arithmetic on quantities, you have 2 equations:

  x = 2kg * 4s
  unit(x) = kg * s = kg.s
  scalar(x) = 2 * 4 = 8
  x = 8 kg.s
Or

  x = 5m / 2s
  x = (5/2) m/s
  x = 2.5 m/s
There is a meaning to units and the operation you do with them. `5m / 2s` is 5 meters in 2 seconds, which is the speed `2.5 m/s`.

`2m + 1s` has no meaning, therefore you can't do anything with the scalar values, and the result remains `2m + 1s`, not `3 (m+s)`.


All unit conversions are actually multiplications by the dimensionless constant 1, i.e., no-ops.

Let's say that you want to convert `2 min` into seconds. You know that `1 min = 60 s` is true. Dividing this equation by `1 min` on both sides is allowed and brings `1 = (60 s) / (1 min)`. This shows that if we multiply any value in minutes by `(60 s) / (1 min)`, we are not actually changing the value, because this is equivalent to multiplying it by 1. Therefore, `2 min = 2 min * 1 = 2 min * (60 s) / (1 min) = 2 * 60 s * (1 min) / (1 min) = 120 s`. We didn't change the value because we multiplied it by 1, and we didn't change its dimensionality ("type") because we multiplied it by a dimensionless number. We just moved around a dimensionless factor of 60, from the unit to the numerical value.

I think that you misremember, or didn't realize that to convert minutes into seconds, you were not multiplying by `60 s` but by `(60 s) / (1 min)` which is nothing else than 1.


What is the "scalar value of the unit"?

Units can be expressed in terms of other units, and you can arbitrarily pick one unit as a base and then express the rest in it. But the key word here is "arbitrarily".

If multiplying by the same unit yield the same unit, then how did you compute area or volume in school?


Wait, would you really expect 1m * 1m to be anything other than 1m²? When does it ever happens that you want to multiply to non-unitless[1] measurements and not multiply the units???

[1] would that be unitful?


I expect 1 * m = 1m, and 1 * m * m = 1m because applying a unit doesn’t inherently have a value of that unit associated with it. (1 m) (1 m) obviously equals 1m^2, but ((1 m) m) is not the same expression.


Since when `((1 m) m)` is a valid mathematical expression?

You cannot have a unit on its own without a scalar value. It makes no sense.


If you look upthread, there was a mention of F# unit types. Taking off my programmer hat and returning to my middle school anecdote which also evidently made no sense: expression of a unit without a value is (or should be to my mind, based on my education) a cast, not a computation of N+1 values.

- 1 is unitless

- 1 * m casts the value to a value 1 of unit m = 1m

- 1 * m * m casts the value 1 * m = 1m to 1m then casts 1m to m which = 1m

Admittedly my educational background here might be wildly unconventional but it certainly prepared me for interoperable unit types as a concept without changing values (~precision considerations).


> If you look upthread, there was a mention of F# unit types.

And the syntax is `3<unit>` not `3 * unit`

- 1 is a scalar - 1m is a quantity - 2 * 1m "casts" 2 to a meter, but really this is just multiplying a quantity by a scalar - 2 * 1m * 1m "casts" 2 to meter², multiplying 2 quantities then by a scalar

I insist, `1 * m` does not make sense. This is not a valid mathematical expression, because a unit can never be on its own without a value.

> expression of a unit without a value is (or should be to my mind, based on my education) a cast

There is no casting in math. Mainly because there is no types, only objects with operations. A vector is not a scalar and you can't cast it into a scalar.

A quantity is not a scalar either, and you can't cast one into another.

A quantity is an object, you can multiply 2 quantities together, but you can't add them if they are different. You can multiply a quantity to a scalar, but you still can't add a scalar to a quantity.


> And the syntax is `3<unit>` not `3 * unit`

Well, yeah, F# represents this at the type level. Which I’ve said elsewhere in the discussion is preferable. Not knowing Go, but knowing it only recently gained generics, I read multiplying by `time.Seconds` (which does not have a visible 1 associated with it) as perhaps performing an operator-overloaded type cast to a value/type with the Seconds unit assigned to it. I’ve since learned that Go also does not support operator overloading, so I now know that wouldn’t be the case. But had that been the case, it isn’t inconceivable that unitlessValue * valuelessUnit * valuelessUnit = unitlessValue * valuelessUnit. Because…

> I insist, `1 * m` does not make sense. This is not a valid mathematical expression, because a unit can never be on its own without a value.

Well, if you insist! But you seem to be imposing “mathematical expression” on an expression space where that’s already not the case? Whatever you may think of operator overloading, it is a thing that exists and it is a thing that “makes sense” to people using it idiomatically.

Even in languages without overloading, expressions which look like maths don’t necessarily have a corresponding mathematical representation. An equals infix operator in maths is a statement, establishing an immutable fact. Some languages like Erlang honor this, many (most? I strongly suspect most) don’t! I couldn’t guess without researching it also treat infix = statements as an expression which evaluated to a value.

The syntax of infix operators is generally inspired by mathematical notation, but it’s hardly beholden to that. The syntax of programming languages generally is not beholden to mathematical notation. Reacting as if it’s impossibly absurd that someone might read 1 * time.Seconds * time.Seconds as anything other than 1 * 1s * 1s is just snobbery.

Not knowing Go, I focused on the syntax and the explicit values, and tried to build a syntax tree on top of it. I’m not a fan of infix operators, and I am a fan of lisps, so my mental syntax model was (* (* 1 time.Seconds) time.Seconds)), which still doesn’t “make sense” mathematically, but it can make sense if `*` is a polymorphic function which accepts unquantified units.


> Not knowing Go, but knowing it only recently gained generics, I read multiplying by `time.Seconds` (which does not have a visible 1 associated with it) as perhaps performing an operator-overloaded type cast to a value/type with the Seconds unit assigned to it.

This sums up your incomprehension. `time.Seconds` is just a constant. An integer with the value `1_000_000` meaning 1 million of nanoseconds.

In an expression of the form `a * b` you should always read `a` and `b` as constants. This is true for EVERY programming language.

> it isn’t inconceivable that unitlessValue * valuelessUnit * valuelessUnit = unitlessValue * valuelessUnit.

It is. For example, what would be the meaning of this:

  struct Foo {
    // ...
  }

  2 * Foo
Valueless unit (or any type) is just not a thing, not in math, not in any programming language.

> But you seem to be imposing “mathematical expression” on an expression space where that’s already not the case? Whatever you may think of operator overloading, it is a thing that exists and it is a thing that “makes sense” to people using it idiomatically.

Operator overloading works on typed values, not "valueless" types. In some programming languages (like Python), class are values too, but why implement `a * MyClass` when you can write `MyClass(a)` which is 100% clearer on the intent?

Using operator overloading for types to implement casting is just black magic.

> expressions which look like maths don’t necessarily have a corresponding mathematical representation

Programming languages and the whole field of Computer Science is a branch of mathematics. They are not a natural language like english or german. They are an extension of maths.

> An equals infix operator in maths is a statement, establishing an immutable fact.

An operator only has meaning within the theory you use it.

For example:

  `Matrix_A * Matrix_B` is not the same `*` as `Number_A * Number_B`
  `1 + 2` is not the same `+` as `1 + 2 + 3 + ...`
  `a = 3` in a math theorem is ont the same `=` as `a = 3` in a programming language (and that depends on the programming language)
As long as the theory defines the operators and the rules on how to use them, it does not matter which symbol you use. I can write a language where you have `<-` instead of `=`, and the mathematical rules (precedence, associativity, commutativity, ...) will be the same.

> Reacting as if it’s impossibly absurd that someone might read 1 * time.Seconds * time.Seconds as anything other than 1 * 1s * 1s is just snobbery.

First, that's not what I said. You should read that as `scalar * constant * constant` because reading that as `scalar * unit * unit` does not make sense nor in math, nor in any programming language.

If caring about readability and consistency is snobbery, then so be it.

> Not knowing Go, I focused on the syntax and the explicit values, and tried to build a syntax tree on top of it.

And the syntax is pretty explicit, because it's the same as math or any programming language: `scalar * constant * constant`. This is why using math as a point of reference is useful, you can easily make sense of what you're reading, no matter the syntax.

> I am a fan of lisps, so my mental syntax model was (* (* 1 time.Seconds) time.Seconds))

I still read this as `(* (* scalar constant) constant))`. And I expect your compiler/interpreter to throw an error if `time.Seconds` is anything without a clear value to evaluate the expression properly.

And I would expect to read `(* (* 1 (seconds 1) (seconds 1)))` as `scalar * quantity * quantity`, and I would expect to get square seconds as an output.

Anything else would not be correct and have little to no use.


You can just do `1 * time.Second + 2 * time.Minute` to do that. Adding times works intuitively. It's multiplying durations that gives you accelerations.


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.


my 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.


I 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.


All 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-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.


And yet people actually do manage meaningful unit systems that don't allow this.


C++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.


Company 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.


All 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.


One 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

C++ chrono::duration allows arithmetic operations with more sensible overloading. https://en.cppreference.com/w/cpp/chrono/duration


Once 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.


You 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.


The 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`.


I'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.


You’re looking at this from the application perspective, where with the specific example of durations constant multiplication is certainly far, far more common. But you’re discounting library concerns, where it would not be out of the ordinary to receive a time.Duration and an int64 from parameters or a struct or similar.

This is also pretty typical of the trade-offs Go makes: it focuses on making things nice for the application writer, mostly pretty successfully, but at the regular cost of pain and with serious typing compromises for the library writer.


What is correct is that duration ± duration = duration, duration * scalar = duration, timestamp ± duration = timestamp, timestamp - timestamp = duration, and anything else doesn't compile.


To 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)


And 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).


not being able to sum timestamps is a bit beyond the scope of units, it is more of a vector vs point distinction


If you exponent time.Duration, that's a user error.

What you're suggesting would be like a compiler error for multiplying two int64s


What 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.


> A physical value is two things:

I would argue it is actually three things: a scalar, a unit, and an indication of error – which is at least another scalar, but there are multiple ways of expressing error, so it might require more than just a single scalar (such as an interval and the probability the actual value lies within that interval.)

> 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

Ideally more than just an enum – Newton = kg*m*s^-2 (equivalently kg^1*m^1*s^-2) – which suggests a set of pairs (unit and exponent).


Yes, thank you for the precisions, this just make my point stronger: int64/float are very ill-suited to represent such values.

And it's especially true for time units.

For example, "how many seconds is one month?" does not make sense, but "how many seconds is january/february/march?" does make sense. The unit "month" does not really exist, each calendar month is its own unit.

And "february" is not even a "stable" unit because sometimes it's 28 days, sometimes it's 29 days. Even a minute can some rare times be 61 seconds.

This is why in physics, we use seconds multiplied by powers of 10 and nothing else.

To my knowledge, there is not a single programming language that differentiate a "scalar" and a "quantity" (scalar, unit, error).


It should be fine to multiply two durations, but it should not return the same time.Duration type


> 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.


You are right however physicists do not multiply duration x duration but time x time. Duration is discrete and time is infinitesimal.


The 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.


I'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



Just contrived examples, like gravity * t^2 to get distance to fall and such, probably


Go's time package is famously horrible. First they didn't expose any monotonic clocks only wall time. Then after some public outages, like time travelling backwards for Cloudflare they were forced to act. In the end they managed to fold monotonic clocks into into the original type to cling on to the "Go just works" mantra, but adding even more edge cases.

https://pkg.go.dev/time#hdr-Monotonic_Clocks

> RRDNS is written in Go and uses Go’s time.Now() function to get the time. Unfortunately, this function does not guarantee monotonicity. Go currently doesn’t offer a monotonic time source (see issue 12914 for discussion).

https://blog.cloudflare.com/how-and-why-the-leap-second-affe...

> time: use monotonic clock to measure elapsed time

https://github.com/golang/go/issues/12914


Rust has a similar-ish API for durations, Duration::from_milis(1000) or Duration::from_secs(1) in the type system, and the method can just take a Duration struct and transform it into whatever internal representation it wants.

There is a Duration::new() constructor that's more ambiguous, but it's your choice as a dev to be ambiguous in this instance, and code review should probably catch that.


Yeah, Rust is one of the few languages that gets it right! And before they had the `Duration` type with `thread::sleep(d: Duration)`, there was `thread::sleep_ms(ms: u32)`, which is also unambiguous.


Or Ruby on Rails:

  sleep(5.seconds)
  sleep(1.minute)
  sleep(2.hours)
etc etc


In Ruby that would be

    sleep 3.seconds
Hard to to be more concise than that


Technically Ruby only accepts seconds. You're thinking of ActiveSupport from Rails.


Here's a fun way to annoy a Rails developer.

    (Time.now + 1.month).to_i == Time.now.to_i + 1.month.to_i
    #=> false


Not sure why that's annoying? The right side doesn't really make sense.

Though it does seem pretty easy for a novice to do thinking it's the same, but what does 1.month.to_i even mean!?


> 1.month.to_i

Duration of a month in seconds? Before you balk at the idea, there exists a definition of a constant month duration for accounting stuff. I you hate dates - and yourself - try accounting, there's mind boggling stuff that makes the engineer mind recoil in absolute terror.


Actually taken almost verbatim from the report summarizing a real and subtle bug (distributed across multiple files) in code written by definitely-not-novices.


I'm not really familiar to RoR. Is is due to Time.now getting called twice and each returns slightly different value?


Alas, no. It's because ActiveSupport's duration arithmetic is not distributive under conversion.

The expression on the left hand side advances time (as a domain object) by exactly a month, then converts the result to integer unix time. The expression on the right adds 2629746¹ to the current unix time.

The conversion becomes dangerously magical in the presence of shared code that accepts both object and integer representations of time & duration. A consumer from one part of a system can inadvertently obtain different results to another unless they use identical calling conventions.

[1] this is 1/12 of the mean length of a gregorian year²

[2] 365.2425 days i.e. 31,556,952 seconds


Oh wow. This is totally make sense when you think about it, but something that'll never cross my mind when casually checking the code. I guess this is why python's timedelta doesn't have month unit as the length of a month is highly context dependent.


I would just use `1.month.from_now` instead of the additions anyway


That won’t save you; 1.month.from_now is implemented by addition.


Are we golfing? Because that's identical to

   sleep 3


Are we golfing? This whole discussion is about clarifying units.


I must clarify, that was intended rhetorically, and in the most self-serving fashion; I try never to miss a golfing prompt


Or Dart

    Future.delayed(const Duration(seconds: 2)
    Future.delayed(const Duration(milliseconds: 2000)


That’s similar to Crystal. All numbers have built in methods to convert them to a Time::Span object. So I could have a function that takes a Time::Span instead of an Int, like:

    def sleep(num : Time::Span)
      # do something here
    end
I would call it like:

    sleep 300.seconds


I'm not sure why but I have a visceral, negative response to this. It might be the best solution, but it definitely /feels/ like the worst of all the worlds.


Is it? In most languages you do something like `time.Sleep(1 * 1e6)` instead at which point it could be a second, a few minutes, a day, who really knows?

I'm just not seeing any major downsides of this, keep in mind `time.Second` isn't the only one of its kind, you have millisecond, minute, hour, etc etc.


Having used it extensively, it's actually quite nice.


Overloading the asterisk is always weird because multiplication is expected to be associative etc etc.


I'm not sure I follow.

2 * 3 * time.Second is the same whether you group 2 * (3 * time.Second) or (2 * 3) * time.Second (namely, the implicit grouping under left-associativity).

You wouldn't normally write time.Sleep(time.Second * time.Second) because your units wouldn't work out. (Apparently you can write that in Golang; it's just very sketchy and results in a 30-year sleep.)


But from a mathematical point of view, the relationship between a unit and its coefficient is that you're multiplying them together. Why would it be weird to overload the multiplication operator to represent multiplication?


There is no operator overloading here. The type Duration is effectively an int64: https://pkg.go.dev/time#Duration


It's not overloaded. It's a unit. You can just type

  time.Sleep(time.Second)
but this reads nicely

  time.Sleep(3 * time.Minute)


time.Second*1 gets the same value; it's not overloaded. Well, ok, so what actually happens is that time.Second is a time.Duration, and Duration*int yields Duration (and int*Duration yields Duration).

But the value of time.Second is actually a Duration with value 1000000, IIRC -- it's microseconds. It's just the type that's special, and the int handling here is general over a lot of types.

It really is nice in practice.


(As noted elsewhere it's nanoseconds.)


It's not really overloading. it is associative:

    2*time.Hour+1*time.second == time.Second+time.Hour*2


I believe that's illustrating commutativity, not associativity.


The illustration is wrong, but the claim is correct; multiplication between units and scalars is just as associative as you'd expect. Multiplying one kilowatt by an hour gives you exactly the same result as multiplying 1 by a kilowatt-hour.


I hope late millenials and generation Z rediscover types soon.


huh? it's us millenials who decided that all dynamic typing was immoral and wrong. Back in the day Gen-Xers on HN and slashdot were talking about how great common lisp and ruby were.


Imma get my cane and hit you with Perl and PHP ;)


Except Common Lisp is typed language (with more expressive type system than most)


And now millennials seem to be doing the same with JS. sigh


Big fan of this.




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

Search: