I'm a big fan of Elixir 1.17's new to_timeout/1 function. However, I find it unnecessarily restrictive for it to only accept integer values in its keyword lists. Consider a simple case like:
to_timeout(hour: 0.5) A lot of the value of the function seems to be in letting devs say "here's what I've got, *you* tell me how to turn it into a timeout()". *An extended example* Another example where the user experience provided by supporting floats is much better: You have a rate limit of 1350 requests per hour for some third party API. (In real life, of course this value comes from an application config variable so that non-developers on the team can change it in prod without needing a code change.) You would like to do a Process.sleep/1 after each request to ensure you stay under the rate limit. With float support, you can do to_timeout(hour: 1 / 1350). Without float support, you can't just convert to 0.044 minutes or 2.67 seconds... you'll need to go all the way to milliseconds. And don't forget that final integer conversion! to_timeout(millisecond: ceil(1 / 1350 * 60 * 60)) At this point, why even bother with the to_timeout function? *Objections* Originally <https://github.com/elixir-lang/elixir/issues/14579>, I had anticipated an objection based on a possible correctness issue—for instance, do you represent 2/3 seconds as 666 or 667 milliseconds? However, these concerns strike me as overblown, since the very nature of a timeout implies some system, somewhere, is going to call you back after a wait. Unless you're on a real-time operating system, even if you ask for exactly 666 milliseconds, you might get your callback in 667 or even 1,667 milliseconds if the system is under heavy load. On the other hand, if you're building something like a pacemaker and every microsecond truly counts, you wouldn't be using a timeout() value (limited to millisecond precision) in the first place. I stand by the idea that since timeouts guarantee a minimum, not a maximum time you'll wait, no one who asks for (say) 2/3 of a second will be shocked when they wait a minimum of 667 milliseconds. It's hard for me to imagine anyone ever noticing. Following that, José pointed out the following objections: 1. to_timeout is meant to take durations and durations do not accept float, so that would make it inconsistent 2. It feels that, once we add this feature, we would need to add huge disclaimers to the function saying "beware of floats" and explain the rounding up behaviour, which makes me wonder what is the benefit of supporting it in the first place. We already support multiple units and you can easily convert from one to the other, so I'd rather write to_timeout(minute: 30) than rely on the impreciseness of floats To #1 I'd say that while I love that the Duration *struct* always represents its time period consistently, I see no reason whatsoever that Duration.new/1 shouldn't also support floats, especially given that Duration already supports microsecond precision. To #2, I don't believe we need huge disclaimers, unless such disclaimers are already necessary when using a Duration with to_timeout. The existing behavior (which presumably hasn't impacted anyone) is actually to *truncate* microseconds down to milliseconds: iex> to_timeout(%Duration{microsecond: {999, 3}}) 0 iex> to_timeout(%Duration{microsecond: {1999, 3}}) 1 However we implement float handling here, I'd argue it should be consistent with Duration's handling of microseconds. (I'm don't have strong feelings as to whether that means to_timeout needs to start doing the ceiling of the number of milliseconds when given a Duration, or whether floats should be truncated to milliseconds. Again, I don't think anyone's going to notice either way.) *Prior art* JavaScript of course does not differentiate between floating point and integer values, so when the setTimeout function accepts a millisecond wait time, you can pass a non-whole value without issue. However, MDN indicates that browsers store the timeout as 32-bit integer milliseconds <https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout#:~:text=Browsers%20store%20the%20delay%20as%20a%2032-bit%20signed%20integer%20internally> internally, so sub-millisecond precision is not respected. However, I can't find any documentation about whether it truncates, rounds, or takes the ceiling of floating point millisecond values (neither on the MDN site, in the spec for the web API <https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-settimeout>, nor in the Node.JS docs). Python's time.sleep() <https://docs.python.org/3/library/time.html#time.sleep> accepts floating point seconds, and the precision of the sleep depends on the operating system, ranging from nanoseconds to microseconds. Ruby's sleep() <https://docs.ruby-lang.org/en/master/Kernel.html#method-i-sleep> also accepts floating point seconds, with an unspecified precision. Go's time.sleep() <https://pkg.go.dev/time#Sleep> accepts integer nanoseconds, but various recommendations I'm seeing around the web suggest getting those nanosecond values by multiplying floating point second values the time.Second constant (the number of nanoseconds in a second), which would result in automatic truncation to nanoseconds. PHP's usleep() <https://www.php.net/manual/en/function.usleep.php> officially accepts integer milliseconds, but if you pass a floating point value, it will automatically truncate it. Java's Thread.sleep() <https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#sleep-long-> accepts integer milliseconds, and potentially also integer nanoseconds. -- You received this message because you are subscribed to the Google Groups "elixir-lang-core" group. To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/elixir-lang-core/10a41104-ea7d-4a68-b99a-b70727f0ad7an%40googlegroups.com.