7 ms·
100%. This is a baseline requirement where I work. If you don't either include the units in the identifier name, or use the type system where possible, your cod
by jeffparsons 5y ago
100%. This is a baseline requirement where I work. If you don't either include the units in the identifier name, or use the type system where possible, your code is not getting merged.
The only people I've ever met that think this is unnecessary are also the same people that ship a lot of bugs for other people to fix.
- Aeolun 5y agoWhat does a type for ‘seconds’ do that an ‘integer’ doesn’t? I may be misunderstanding this.
- lrem 5y agoYou can construct a linter that will prevent you from trying to add seconds to dollars.
- akdor1154 5y agoA better example would be seconds to minutes. I think i recall a jwt related cve related to timestamps being misinterpreted between sec/ms for example.
- taneq 5y agoAdding seconds to minutes can actually make sense. A minute plus 30 seconds is 90 seconds, or 1.5 minutes. Whether your type system allows this, though, is up to the project. You can't add seconds to kilometers, or to temperature, or to how blue something is.
- OtomotO 5y agoIdeally you have a compiler that simply refuses to compile ambiguous code
- eurasiantiger 5y agoIdeally, there is no compiler.
- _flux 5y agoIdeally, you would detect all errors before runtime. Usually the compiler is the last gate to make that happen.
- eurasiantiger 5y agoIdeally, hardware execution would happen on a language that can be proven correct and does not allow the programmer to make syntactic or semantic errors. The world is far, far from ideal.
- CamperBob2 5y agoIsn't there an implicit conversion?
- davisoneee 5y agoif you type check, then it ensures that only a 'second' can be passed to the function. This requires you to either create a second, or explicitly cast to one, making it clear what unit a function requires. As per the article, if you dont have proper names and just an 'int', that int can represent any scale of time...seconds, days, whatever. In python youd need something like mypy, but in rust you could have the compiler ensure you are passing the right types.
- huynhhacnguyen 5y agoHaving a type system to figure this out for us would be great, but there are languages where this may not be possible. As far as I know, Typescript is one such example, isn't it?
- kaoD 5y agoDepends. Yes, newtyping is pretty awful in TS due to its structural typing (instead of nominal like Rust for example). You could perhaps newtype using a class (so you can instanceof) or tag via { unit: 'seconds', value: 30 } but that feels awful and seems to be against the ecosystem established practices. This is indeed one of my gripes with TS typing. I'm spoiled by other languages, but I understand the design choice.
- dbrgn 5y agoYou can do something like this: export interface OpaqueTag<UID> { readonly __TAG__: UID; } export type WeakOpaque<T, UID> = T & OpaqueTag<UID>; And then use it like this to create a newtype: export type PublicKey = WeakOpaque<Uint8Array, {readonly PublicKey: unique symbol}>; To create this newtype, you need to use unsafe casting (`as PublicKey`), but you can use a PublicKey directly in APIs where a Uint8Array is needed (thus "weak opaque").
- spion 5y agoI tried to solve this problem in a way that's reasonably pleasant here https://github.com/spion/branded-types https://github.com/spion/branded-types
- kitd 5y agoA function might take multiple integer arguments, each in different units. Separate types for each unit guarantees you won't pass the wrong integer into the wrong argument. Eg func transferRegularly(dollars(10000), days(30)) Meaning clear. func transferRegularly(10000, 30) Meaning obscure, error prone and potentially costly
- deleted 5y ago[deleted]
- necovek 5y agoWith some languages like Python, you can use keyword arguments too (even out of order). Eg. you could simply do transferRegularly(amount=10000, period_in_days=30) I am always amazed how new languages never pick up this most amazing feature of Python. Though obviously, this code smells anyway because 1. repetitive transfers are usually in calendar units (eg. monthly, weekly, yearly — not all of which can be represented with an exact number of days), so in Python you'd probably pass in a timedelta and thus a distinct type anyway, and 2. amounts are usually done in decimal notation to keep adequate precision and currency rounding rules (or simply `amount_in_cents`). Still, I am in favour of higher order types (eg. "timedelta" from Python), and use of them should be equally obligatory for unit-based stuff (eg. volume, so following the traditional rule of doing conversions only at the edges — when reading input, and ultimately printing it out).
- dotancohen 5y agoPHP got this property in PHP 8. One problem with this approach is refactoring. If you wanted to refactor your example with the parameter "amount_in_dollars" then you would either have to continue maintaining the legacy "amount" argument, or break existing code.
- necovek 5y agoSo you mean just like with, eg. renaming a function? I agree it's an issue compared to not doing it, but a very, very minor one IMHO, and legibility improvements far outweight it.
- atoav 5y agoWell your function could then accept multiple types (Miliseconds, Seconds, Minutes, Hours) and do the conversion between those implicitly. The units are also extremely clear when they are carried by the type instead of the variable name, where a developer could e.g. change some functionality and end up with a variable called `timeout_in_ms` while the function that eats this variable might expect seconds for some reason. If it is typed out you can just check if the function performs the right action when passed a value of each time unit type and then you only ever have to worry about mistakingly declaring the wrong type somewhere. But wether you should really do all that typing depends on how central units are for what you are doing. If you have one delay somewhere, who cares if it is typed or not. If you are building a CNC system where a unit conversion error could result in death and destruction, maybe it would be worth thinking about.
- Cthulhu_ 5y agoWhat does the integer represent? Nano, milli, microseconds, seconds, minutes, hours, days?
- jsnell 5y agoMost answers here are answering your question literally, and explaining why you'd add a type for "seconds". But in reality you shouldn't create a type for seconds, the whole premise of the question is wrong. Instead of a type for seconds, you'd want a type for all kinds of duration regardless of the time unit, and with easy conversion to integers of a specific time unit. So your foo() function would be taking a Duration as an input rather than seconds, and work correctly no matter what unit you created that duration from: foo(Duration.seconds(3600)) foo(Duration.hours(1)) foo(Duration.hours(1) + Duration.seconds(5))
- indymike 5y agoTells you what the integer represents, so you aren't off by several orders of magnitude.
- GuB-42 5y agoThe type is "duration", not "seconds". "seconds" is the unit. You can think of the unit as an operator that converts an integer to a duration. The advantages are: - An integer doesn't tell you if you are talking about seconds, milliseconds or anything like that. What does sleep(500) means? Sleep 500s or 500ms? sleep(500_ms) is explicit - It provides an abstraction. The internal representation may be a 64-bit number of nanoseconds, or a 32-bit number of milliseconds, the code will be the same. - Conversions can be done for you, no more "*24*60*60" littered around your code if you want to convert from seconds to days, do fun(1_day) instead. - Safety, prevents adding seconds to meters for instance. Ideally, it should also handle dividing a distance by a duration and give you a speed and things like that. Under the hood, it is all integers of course (or floats), which is all machine code, but handling units is one of the things a high level language can do to make life easier on the human writing the code.
- nightfly 5y agoWhat type of industry/product do you work in/on? And what sort of languages do you work in?
- DocTomoe 5y agoNot OP, but I see that a lot in the aerospace and heavy industry sectors. We keep laughing about "if we engineered bridges as we engineer software" ... the truth is that the areas where correct software matters tend to write very robust code, and the rest of the industry would be well advised to take notice and copy what they see. Of course, writing robust code is a skill, and it takes extra time.
- mhaberl 5y ago> and the rest of the industry would be well advised to take notice and copy what they see I don't agree. There is a good reason that aerospace industry writes robust code - in invests time (money) to avoid disasters that could cause, among other things, loss of human life. On the other hand if for example some webform validation fails because the code was written as fast (as cheap) as possible, who cares really. That is just a tradeoff, in aerospace industry you spend more to lower the risk, somewhere else you don't.
- thfuran 5y ago>On the other hand if for example some webform validation fails because the code was written as fast (as cheap) as possible, who cares really. Who knows. Maybe a billion dollar company that can't fulfill orders. Maybe a million people who suddenly can't use their bank online.
- michaelt 5y ago> The only people I've ever met that think this is unnecessary are also the same people that ship a lot of bugs for other people to fix. I find feelings about this depend a lot on how much ceremony the type system involves; and just how many units and references to them there are in the system. Asking bash coders to write "sleep 5s" instead of "sleep 5" - I doubt you'd get any objections at all. But if you're putting a foo.bar.Duration on a getDefaultTimeoutFromEnvironment on a setConnectTimeout on a HttpRequest on a HttpRequestInitializer on a NetHttpTransport on a ProviderCredential to make a simple get request? People who've come from less ceremony-heavy languages might feel less productive, despite producing 10x the lines of code.
- contravariant 5y agoWell ignoring the silly stuff this would just boil down to something like: request = NetHttpTransport.Request() request.setConnectTimeout(getDefaultTimeoutFromEnvironment().seconds) which is verbose, but at least it's fairly clear. And yes I'm calling a class named HttpRequestInitializer silly, I don't care if some language decided it should exist.
- sethammons 5y agoThis is where I divert. You just hard coded seconds into your test. Now your tests that cover this must take seconds to finish; thankfully you were not testing hours or days! My last shop was a Go shop and one test I think shows this off was an SMTP server and we needed to test the timeouts at different states. The test spun up half a dozen instances of the server, got each into the right smtp state and verified timeouts in under 10ms. The environment that set the timeout would either be "timeout_ms=500" or "timeout=500ms" (or whatever). This is where you handle that :)
- catlifeonmars 5y agoThis is actually revealing a different problem: the system clock as an implicit dependency. YMMV depending on support of underlying libraries, but I will typically make the clock an explicit dependency of a struct/function. In Go, usually it’s a struct field `now func() time.Time` with a default value of `time.Now`.