5 ms·
Built-in unit types are underrated. F# has them. Other languages should seriously consider adding them, despite the fact that feature bloat is a serious languag
by armchairhacker 5y ago
Built-in unit types are underrated. F# has them. Other languages should seriously consider adding them, despite the fact that feature bloat is a serious language problem and they're relatively niche: they are that useful, and I honestly don't think they would interfere much with other features.
Something like:
unit type Meters = m
unit type Seconds = s
function sleep(time: Int[s]): void
val speed = 5.4 m/s // Type = Float[m/s]
val distance = parseFloat(prompt('enter time')) * 1 m // Convert unitless to meters just by multiplying
val time = distance / speed // Type = Float[s]
print("${time}") // Prints "# s"
print("${time / 1 s} seconds") // Prints "# seconds"
val complexUnit = 7 * 1 lbf/in2 // 7 lbf/in2
// != 7 psi (too hard to infer) but you can write a converter function
function toPsi<N : Numeral>(value: N[lbf/in2]): N[psi] {
return value * 1 psi*in2/lbf
}
It requires extending number parsing, type parsing (if the brackets aren't already part e.g. in TypeScript), and extending types to support units of measurement at least if they are statically known to be subtypes of `Numeral`.
Naming variables with their units doesn't solve the issue of mis-casting and using units incorrectly, and newtypes are too inconvenient (and sometimes impossible without affecting performance) so nobody uses them. Even as a very software-focused programmer I encounter units like seconds, bytes, pixels, etc. all the time, they are almost never newtypes, and I get bugs from forgetting to convert or converting incorrectly.
- lf-non 5y agoYup, I have been using branded types in TS to avoid similar issues, and I still do miss F# units of measure. Even though branded types prevent assignment of values with invalid units, opaque types are inferior in that the compiler doesn't know the relationship between the types (as exemplified in the parent comment) and you need a slew of helper functions & casting.
- eyelidlessness 5y agoI use a variant of branding in TS for this too, but it’s very much shoving semantics into the language that aren’t meant to be there. Subclasses of primitive types would be semantically closer to F#, but at the expense of being able to ever treat the values as values
- WorldMaker 5y agoI just realized that you could probably do a decently complex unit algebra (at least to the basic level of F#'s unit of measure support) using the unit strings as brands and Typescript's template string types to match them. I'm not sure if that's a good idea or not in practice, but after seeing Wordle done with template string types I'm more certain than ever it is possible to do some interesting things with it.
- cycomanic 5y agoSo from your example I understand that F# doesn't know how to do more complex unit conversion? Does it know N=kg m/s^2 for example? That's related to the issue I often encountered with unit types. They work great for simple use cases, and give you a sense of security, but then fail and you end up writing lots of code just to make the unit types happy (you might argue that it makes the code safer, but it can be a lot of time spend). One cthing that falls over in pretty much all implementations I've encountered is log units (dBm, dBW,...)
- int_19h 5y ago> Does it know N=kg m/s^2 for example? F# does if you tell it, i.e.: [<Measure>] type kg = g [<Measure>] type m [<Measure>] type s [<Measure>] type N = kg * m / s^2 Although, of course, in practice you'd probably just use whatever's defined in https://fsharp.github.io/fsharp-core-docs/reference/fsharp-data-unitsystems-si-unitsymbols.html https://fsharp.github.io/fsharp-core-docs/reference/fsharp-d... Not for log units tho.
- deleted 5y ago[deleted]
- eurasiantiger 5y agoIn the land of JavaScript, one could implement unit types as a babel plugin.
- _flux 5y agoGoing from "could" to actually doing it are two very different things, however.
- lf-non 5y agoI don't see how you'd go about doing this. Babel doesn't have a type checker, and for type level features you'd need support from typescript. So you need a typescript plugin - however the typescript compiler api (which btw is not officially supported) is not flexible enough to support new type syntax (for things like m/s) so you'd a fork.
- eurasiantiger 5y agobabel-plugin-typecheck, babel-plugin-tcomb and babel-plugin-jsdoc-type-checker (among others) are able to do similar things, so there must be a way. And I don’t see why not, since babel is ”just” an AST parser/transformer.
- lf-non 5y agoThose plugins primarily help with with runtime type validations - the static type checking is delegated to Flow/TS. The benefits around F# units of measure that the parent talks about are primarily focused on static type checking. If you are fine with the overhead of runtime boxing, it is relatively straightforward to have different container classes for different units and have bridge apis to convert/operate between them. A babel plugin could potentially help with operator overloading here, but this is quite far removed from F# units of measure at this point.
- eurasiantiger 5y agoIs writing a static unit type checker as a plugin a daunting task, then? I’ve never thought of it in detail, but it would seem that unit type checking rules for basic operations like add/subtract and multiply/divide would be straightforward, and everything else could be derived from there. The laborious task is just formulae.
- gpderetta 5y agoIn C++ units can, and often are, implemented on top of the existing type system. See std::duration/time_point specifically for times, and boost.units for generalized unit support. It is implemented via (templated) wrappers, but they are very close to zero overhead if not completely free. User defined literals also allow for a very succinct syntax, but to be honest, I usually don't bother. I normally write: auto delay = std::chrono::seconds{3}; instead of: auto delay = 1s;
- deleted 5y ago[deleted]
- ChrisLomont 5y agoIn C++ you can use user defined literals to make the units stuff compile time checked, similar to this https://github.com/bernedom/SI https://github.com/bernedom/SI for example.
- roeles 5y agoUsing templates I once implemented logic for dealing with SI units[1]. I also included angles, which is a bit odd but useful in practice. It is really nice to have a type system that checks your formulas. Saved me a couple of times. [1] https://gitlab.com/roeles/zen/-/blob/master https://gitlab.com/roeles/zen/-/blob/master
- roeles 5y agohttps://gitlab.com/roeles/zen/-/blob/master/src/physics/unit.hpp https://gitlab.com/roeles/zen/-/blob/master/src/physics/unit...
- perth 5y agoMy Ti-89 has this. It's actually really useful because I don't always remember the magical ways units fit together and it solves and simplifies the units for me.
- gilcot 5y agoLong before that, very flexible units management was seen on HP-28 then HP-48 series.
- aspaceman 5y agoSee https://unyt.readthedocs.io/en/stable/ https://unyt.readthedocs.io/en/stable/ in Python. This sort of feature fits really well within Python's featureset.
- saeranv 5y agoCan someone break this down for me? val speed = 5.4 m/s // Type = Float[m/s] I understand that the speed variable is automatically assigned a type of Float[m/s] based on the m/s there, but I'm confused about how the units are just placed at the end of the value assignment: val speed = {value} {unit} Is this just a F# property that units can be added at the end of value assignment, and F# interprets them as units correctly? Also, anyone know of methods for incorporating units as types in Python? It would be great to have an equivalent method as demonstrated here, where dividing types automatically creates a new type unit, and perhaps even associates with a related type, i.e: type(kg / (m * m * m)) == density
- AaronFriel 5y agoI think the syntax for expressions was extended to allow a `<unit expression>`, with or without angle brackets, at the end of any numeric expression. They're erased at compile time, so at runtime or via reflection you can't inspect the dimensions of a value. The only value of the units - which isn't insignificant - is the compile time checking. Unfortunately the same isn't as easily done in Python: 1. You would need to reify these units as actual constants with overridden operators to track values, and some wacky (if even possible) mypy type shenanigans. 2. The types aren't erased and so would impact the performance of any critical code. F# isn't quite used in high frequency trading, but it does target numeric heavy users like those wanting to write scientific workloads, and units help improve correctness there. But if the types are computed at runtime and not erased, it would be an enormous hit to performance. Unless you can figure out some way to get "3.0 * m" to be a plain old datatype (float) in Python while still retaining type info in mypy/pylance/pyrite. Perhaps there's a way, but I'm not sure. Edit: This Python library seems to be trying to solve for these issues! https://pint.readthedocs.io/en/0.10.1/ https://pint.readthedocs.io/en/0.10.1/ I'm not sure if they've actually solved the runtime performance problem, but I suppose as long as you're not doing unit assignment/conversion in a loop, it should be fine.
- stefs 5y agoyour example is a bit confusing because you mislabeled the prompt for distance in val distance = parseFloat(prompt('enter time')) * 1 m // Convert unitless to meters just by multiplying