15 ms·
I would go one step further and suggest that all physical quantities should either have the units in the identifier name or encoded in the type system. Meters,
by wyldfire 5y ago
I would go one step further and suggest that all physical quantities should either have the units in the identifier name or encoded in the type system. Meters, seconds, milliamps, bytes, blocks, sectors, pages, rpm, kPa, etc. Also it's often useful to explicitly distinguish between different unit qualifications or references. Seconds (duration) versus seconds-since-epoch, for example. Bytes versus page-aligned bytes, for another.
Having everything work this way makes it so much easier to review code for errors. Without this means that as a reviewer you must either trust that the units and conversions are correct or you should do some spelunking to make sure that the inputs and outputs are all in the right units.
- jeffparsons 5y ago100%. 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`.
- jweir 5y agoThe wonder elm-units is such a pleasure to work with and does just that. https://package.elm-lang.org/packages/ianmackenzie/elm-units/latest/ https://package.elm-lang.org/packages/ianmackenzie/elm-units... Even if you don’t work in Elm take a moment to look at it.
- HideousKojima 5y ago>Having everything work this way makes it so much easier to review code for errors. Had one making me pull my hair put the other day in C#. C# datetimed are measured in increments of 100 nanoseconds elapsed since January 1st 1 AD or something like that. Was trying to convert Unix time in milliseconds to a C# datetime and didn't realize they were using different units. My fault for not reading the docs but having it in the name would have saved me a lot of trouble.
- kqr 5y agoI'm not sure I agree. When I convert fields to DateTime, I remove the unit suffix from the name. The DateTime is supposed to be an implementation-agnostic point in time. It shouldn't come with any units, and nor should they be exposed by the internal implementation. The factory method used to convert e.g. Unix timestamps to DateTimes, now that should indicate whether we're talking seconds or milliseconds since epoch, for example, and when the epoch really was.
- gregmac 5y agoThey do: .ToUnixTimeSeconds() .ToUnixTimeMilliseconds() DateTimeOffset.FromUnixTimeSeconds(Int64) DateTimeOffset.FromUnixTimeMilliseconds(Int64) https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.tounixtimeseconds?view=net-6.0 https://docs.microsoft.com/en-us/dotnet/api/system.datetimeo... https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.fromunixtimeseconds?view=net-6.0 https://docs.microsoft.com/en-us/dotnet/api/system.datetimeo...
- stevesimmons 5y agoHow does the C# DateTime type distinguish between dates and a point in time whose time just happens to be midnight? Much of the C# code I've seen uses names like start_date with no indication of whether it really is a date (with no timezone), a date (in one particular timezone), or a datetime where the time is significant. I'm certainly not a C# developer, though my quick reading of the docs suggests that the DateOnly type was only introduced recently in .NET6.
- philsnow 5y agoHaving units as part of your types improves legibility for whoever's writing code too, not just reviewing. You won't make (as many) silly mistakes like adding a value in meters to another in seconds.
- EdwardDiego 5y agoGod I love F#'s unit types. C# is okay and all, but F# is, IMO, the best FP-language-on-a-corporate-backed-VM ever, even if the integration with the underlying VM and interop can get a bit fiddly in places (Caveat, my opinion is about 7 years old). Yeah, you heard me Scala.
- pjmlp 5y agoSadly it hardly got better, C# gets all the new .NET toys, VB less so, C++/CLI still gets some occasional love (mostly due to Forms / WPF dependencies), and then there is F#.
- deepsun 5y agoNitpicks: Why would your type system have encoded unit for kilo-pascal, but not hecto-pascal, mega-pascal, micro-pascal etc? If you only encode base units (e.g. seconds), then we should use exact-precision arithmetic instead of f32 or f64, which is sometimes an overkill. If encoding all the modulos (kilo/milli/mega etc) I feel like there are some units may have name clashes (e.g. "Gy" -- is it giga-years, or gray)? Should we encode only SI units, or pounds/ounces/pints as well?
- Ekaros 5y agoTime units are messy. Once we get to Gigayears, what is the size of single year? Just 365 days? Or which of Julian, Gregorian, Tropical, Sidereal? At even kilo prefix the differences do add up. Or would you need to specify it? Days, weeks and months are also fun mess to think of.
- BlueTemplar 5y agoThis is going to depend on the precision that you need. (Calculations without associated uncertainty calculations are worse than worthless anyway - misleading due to the inherent trust we tend to put in numbers regardless of whether they are garbage.)
- dragonwriter 5y ago> Once we get to Gigayears, what is the size of single year? The appropriate system of units is context-dependent. The astronomical system, for instance, has Days of 86,400 SI seconds and Julian years of exactly 365.25 Days; if you have a general and extensible units library, then this isn't really a difficulty, you just need to make a choice based on requirements.
- Juliate 5y agoWhy wouldn't the type system be able to take care of that?
- stinos 5y ago(e.g. "Gy" -- is it giga-years, or gray) In my opinion this is not a real problem, since ideally no-one should such meaningless abbreviations in code. Just write giga_years or GigaYears or whatever your style is, problem solved, doesn't get any clearer than that.
- sandreas 5y agoFor everyone using Python and willing to try this: https://pint.readthedocs.io/en/stable/ https://pint.readthedocs.io/en/stable/
- DiogenesKynikos 5y agoThe Python package astropy extends numpy arrays to include units.[0] It can convert between equivalent units (e.g., centimeters and kilometers), and will complain if you try to add quantities that aren't commensurate (e.g., grams and seconds). The nice thing about this is that you can write functions that expect unit-ful quantities, and all the conversions will be done automatically for you. And if someone passes an incorrect unit, the system will automatically spit out an error. 0. https://docs.astropy.org/en/stable/units/index.html https://docs.astropy.org/en/stable/units/index.html
- Beltiras 5y agoThis is just about the best justification I have heard for type calculus.
- dgb23 5y agoYou don't need a type system to do this. Generic operators/functions and composite structures are sufficient and more flexible. Some languages let you encode this in type systems as well, but that's an orthogonal feature.
- bnegreve 5y ago> physical quantities should [...] be encoded in the type system. But types are not just useful to specify the content of a variable, they are also useful to specify the required precision. So, if there is a type for seconds in the type system, should it be a 32 bits int or a 64 bits float? Only the user can say.
- valleyer 5y agoA generic type would be useful then: type DurationSeconds<Value> where Value: Numeric { ... } DurationSeconds<UInt32> DurationSeconds<Float64>
- account42 5y agoThis is exactly what C++ 11 did with std::chrono [0] except it goes one step further and makes the period generic too. [0] https://en.cppreference.com/w/cpp/chrono/duration https://en.cppreference.com/w/cpp/chrono/duration
- geokon 5y agoI'm sorry, but this whole comment section is in some collective psychosis from 2010. You don't need to mangle variable names or create custom types (and then the insane machinery to have them all interact properly) Have none of you ever refactored code..? Or all of your writing C? Your library should just expose an interface/protocol signature. Program to interfaces. Anything that is passed in should just implement the protocol. The sleep function should not be aware or care about the input type AT ALL. All it requires is the input to implement an "as-milliseconds()" function that returns a millisecond value. You can then change any internal representation as your requirements change
- qayxc 5y ago> Have none of you ever refactored code..? Or all of your writing C? You seem to ignore the much broader context here: the article wasn't just about code. It included configuration files and HTTP requests. It's also worth noting that depending on the language in question and the usage pattern of the function/method, using interfaces or protocols can cause considerable overhead when simply using a proper descriptive name is free.
- ReleaseCandidat 5y ago> [...] create custom types (and then the insane machinery to have them all interact properly) That's an argument against languages with type-systems that make this a PITA, not against the idea itself.
- pif 5y ago> You don't need to ... create custom types > > All it requires is the input to implement an "as-milliseconds()" function A.k.a. custom type!
- flohofwoe 5y agoThat's also a custom type. But for libraries, custom types for everything in interface definitions are problematic. Let's say you use library A which has some functions that take a time delta, and another library B, that also has functions which take time deltas. Now both library would define their own time delta type, and you have two types for time deltas in your application and most likely need to add an additional 'meta time delta type' which can be converted to the library specific time deltas types. This will explode very quickly into lots of boilerplate code and is a good reason to use 'primitive types' in interfaces. If you replace 'time delta' with 'strings' then the problem becomes more apparent. Each library defining its own string type would be a mess.
- Gigachad 5y agoI love when types are used to narrow down primitive values. A users id and a post id are both numbers but it never makes sense to take a user id and pass it to a function expecting a post id. The code will technically function but it’s not something that’s ever correct.
- cb321 5y agoYou might find this interesting: https://github.com/SciNim/Unchained https://github.com/SciNim/Unchained
- ReleaseCandidat 5y agoI _really_, _really_ like F#'s 'unit of measure': https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/units-of-measure https://docs.microsoft.com/en-us/dotnet/fsharp/language-refe...
- fluid_ident 5y agoThis. I really miss it when working outside of F#. I work on scientific code bases with a lot of different units and have been burned by improper conversions. Even with a high automated test coverage and good naming practices, such problems can go undetected.
- ok123456 5y agoIt's a big omission that it doesn't support fractional units. These come up in things like fracture mechanics for stress intensity.
- ReleaseCandidat 5y agoAre you talking about ksi√in or MPa√m? That is not a problem.
- ok123456 5y agoF#'s units don't support 1/2 dimensions. So you can't do dimensional analysis through the type system.
- necovek 5y agoIf your use of a value in units lives for so long as for it to not be clear in what unit it is in or should be in (eg. spans more than 20 lines of code), I think you've got a bigger problem with code encapsulation. I think the practical problem stems from widespread APIs which are not communicating their units, and that's what we should be fixing instead: if APIs are clearer (and also self-documenting more), the risks you talk of rarely exist other than in badly structured code. Basically, instead of having `sleep(wait_in_seconds)` one should have `sleep_for_seconds(wait_time)` or even `sleep(duration_in_seconds=wait_time)` if your language allows that. But certainly use of proper semantic types would be a net win, but they usually lose out in the convenience of typing them out (and sometimes constructing them if they don't already exist in your language).
- teddyh 5y agoThere are usually libraries for doing this. For Python, there are several, for instance “Pint”.
- BlueTemplar 5y agoI was excited when I found Pint, but then was disappointed : too much extra overhead for the project I was then working on. (I settled on units in variable names instead, was essential when some inputs were literally in different units of the same physical dimension.) EDIT : more on this, and on astropy (which I was not aware of and/or didn't exist back then) : https://github.com/astropy/astropy/issues/7438 https://github.com/astropy/astropy/issues/7438
- ranaexmachina 5y agoI think you would enjoy programming in Ada.
- ajdude 5y agoWhen I first started to learn Ada, I found it one of the most verbose languages I’ve ever used. Now I find myself practicing those wordy conventions in other languages too.
- wildzzz 5y agoIn my software project, all measurements exist as engineering units since they all come from various systems (ADCs or DSP boxes). We pass the values around to other pieces of software but are displayed as both the original values and converted units. We have a config file that contains both units and conversion polynomials, ranging from linear to cubic polynomials. Some of the DSP-derived values are at best an approximation so these have special flags that basically mean "for reference only". Having the unit is helpful for these but are effectively meaningless since the numbers are not precise enough, it would be like trying to determine lumens of a desk lamp from a photo taken outside of a building with the shades drawn.
- albrewer 5y agoHave you come across a system or language that handles units and their combinations / conversions well? I have a project I want to undertake but I feel like every time I start to deal with units in the way I feel is "proper" I end up starting to write a unit of measure library and give up.
- wpietri 5y agoAnd let's not forget money. Martin Fowler has been (correctly) banging this drum for years: https://martinfowler.com/eaaCatalog/money.html https://martinfowler.com/eaaCatalog/money.html
- Too 5y agoAdd to this any type of conversion or relations between units. PIXELS_PER_INCH or BITS_PER_LITER rather than SCREEN_SCALE_FACTOR and VOLUME_RESOLUTION, avoids all kinds of mistakes like inverting ratios etc.
- valbaca 5y ago> Option 2: use strong types