Stream: ideas

Topic: Built-in units of measure


view this post on Zulip Martin Stewart (Jul 01 2022 at 13:10):

What if all the number types had a type variable for defining their unit of measure? For example, Num Seconds *, Float Meters *, or Int16 Bytes In other words, the extra type variable would be a phantom type that prevents you from mixing up different units.

If the user isn't interested in adding units then it isn't much effort to write Num * * but for someone who does want to use units of measure, they no longer are penalized by needing to convert between Num and their own Quantity type whenever they need to use the number with 3rd party code. In Elm, this is something I run into quite often. For example, I'd like to use Quantity Pixels number when working with html, but I usually end up not doing it because all the html functions want a raw int or float value.

Additionally, having units of measure built into Num means that it pushes package authors towards better documenting what kind of values the API uses. Using Elm as an example again, there's Process.sleep : Int -> Task x a. The type signature doesn't indicate if Int is milliseconds or seconds. With the extra type variable it could be Process.sleep : Int Seconds -> Task x a

There are some drawbacks:

view this post on Zulip Anton (Jul 01 2022 at 13:26):

Interesting idea

view this post on Zulip Brendan Hansknecht (Jul 01 2022 at 14:38):

This definitely feels like it could fall into the category of the pit of success. Though it also feels like it might be quite annoying seeing I64 * all over code without units. Just adds noise. Even seeing I64 Meters is quite odd looking. Though they would probably just get aliases.

view this post on Zulip Anton (Jul 01 2022 at 18:16):

On a related note, I wonder how hard it would be to implement roc with racket. racket supposedly makes it easy to implement a language, of course not with the same execution speed as our approach. It might be a good foundation to prototype ideas like this one from @Martin Stewart.

view this post on Zulip jan kili (Jul 01 2022 at 20:49):

I don't know what phantom types are or how they work, but the proposal above sounds/looks awesome. More self-documenting and less mistake-prone.

view this post on Zulip jan kili (Jul 01 2022 at 21:38):

g : F64 MetersPerSecondSquared
g = 9.8

calculateFallTime : F64 Meters -> F64 Seconds
calculateFallTime = \stationaryAltitude ->
    Num.sqrt (stationaryAltitude * 2 / g)

:smiley:

view this post on Zulip Hashi364 (Jul 01 2022 at 21:41):

Maybe, it could be a wrapper? Something like Measure I64 Meters.
This could generalize for any type, not sure if it is a good or bad thing.

view this post on Zulip Brian Carroll (Jul 01 2022 at 22:32):

Yeah and Roc will actually optimize the wrapper away, so it will have no performance cost.
There's a library in Elm that does this, elm-units
The trick is that if you want to do arithmetic on Measures, you have to define functions like add that unwrap and re-wrap the number. (elm-units does that here)
But the Abilities feature that is under development might make it so that we could use all the Num operators like + and - directly on user-defined types.

view this post on Zulip Brian Carroll (Jul 01 2022 at 22:34):

Here's a post on phantom types in Elm https://thoughtbot.com/blog/modeling-currency-in-elm-using-phantom-types

view this post on Zulip Martin Stewart (Jul 02 2022 at 11:55):

The trick is that if you want to do arithmetic on Measures, you have to define functions like add that unwrap and re-wrap the number. (elm-units does that here)
But the Abilities feature that is under development might make it so that we could use all the Num operators like + and - directly on user-defined types.

I was thinking abilities might make having a Measure type work just as well as adding a "units" type var to the core number types, but is it always the case?

For example, if someone makes a rigid body physics package, I'm guessing they are probably going to pick a concrete number type like F32 or F64, rather than a generic Float a since if they'd need to include that a type var throughout all their types and functions. And once they are using F32 or F64 then you'll be stuck unwrapping and wrapping your Measure type.

view this post on Zulip Brian Carroll (Jul 02 2022 at 12:18):

No, not if it has the arithmetic ability

view this post on Zulip Brian Carroll (Jul 02 2022 at 12:19):

Which would be how all the built-in types get their ops too

view this post on Zulip Brendan Hansknecht (Jul 02 2022 at 15:14):

I don't think arithmetic will allow implicit conversion. So it would allow a + a but not a + b. So I think you would have to unwrap if you have a Measure F32 Meters and the physics package just takes an F32.

As with the example of Float a vs specifically F64 or F32. I am pretty sure that if a package author picks F32 you will still be stuck with it or have to call Num.toF64 if you want to use F64 instead.

So I don't think abilities fix this.

view this post on Zulip Brian Carroll (Jul 02 2022 at 15:19):

That presumably is the behaviour you'd want though! you want to be able to add metres together easily but you want a type error if you try to add metres to "just numbers", otherwise what's the point?

view this post on Zulip Brian Carroll (Jul 02 2022 at 15:19):

As I understand it, the point of this kind of thing is to make sure metres only add to metres

view this post on Zulip Brian Carroll (Jul 02 2022 at 15:20):

If they all coerce to f64 then why bother with any of this at all?

view this post on Zulip Brendan Hansknecht (Jul 02 2022 at 15:22):

Yes, totally agree. But if using Measure is uncommon cause it is not standard, I think that you will run into a problem where to use a library your are just constantly unwrapping and rewrapping cause no libraries use Measure.

So I think this shows the advantage of adding units to the core number types over just leaving people to make their own Measure type.

view this post on Zulip Brian Carroll (Jul 02 2022 at 15:24):

Yes but in that case what's the advantage over just using F64 everywhere?

view this post on Zulip Brendan Hansknecht (Jul 02 2022 at 15:25):

So the idea is you can't just use F64. it has to be F64 Something. So everyone has to always think about types.

view this post on Zulip Brian Carroll (Jul 02 2022 at 15:25):

But I thought your motivating example above was exactly the case where they don't have to think about that

view this post on Zulip Brian Carroll (Jul 02 2022 at 15:26):

because it just coerces anyway

view this post on Zulip Brendan Hansknecht (Jul 02 2022 at 15:27):

Maybe I missed something or am mixed up.

view this post on Zulip Brian Carroll (Jul 02 2022 at 15:30):

OK so in the context where this sort of thing is useful, the fact that the measures are wrapped is seen as an advantage, not a disadvantage.
You want it to be inconvenient to add things that have different units! Because that's probably a mistake.
So anything you do to make it easy to unwrap to an F64 defeats the goal of making the bad thing difficult.

view this post on Zulip Brendan Hansknecht (Jul 02 2022 at 15:31):

Yes

view this post on Zulip Brendan Hansknecht (Jul 02 2022 at 15:34):

You just have the problem that a physics library for example is likely to use a regular F32, instead of a Measure type. which means you are likely to do a lot of unwrapping anyway.

So using a measure type makes it more likely that you either can't use a library or need to unwrap and rewrap around every call.

view this post on Zulip Brian Carroll (Jul 02 2022 at 15:38):

Yes, there are some situations where this whole idea of measures is not suitable. Agreed.
In those situations the only solution is to remove the wrapper... which makes them pointless because now you have nothing to prevent any errors.

view this post on Zulip Brendan Hansknecht (Jul 02 2022 at 15:39):

If instead, we had F32 Meters, all libraries would either use F32 * or F32 something.

Now if I am using F32 Meters. By default, I can use any library that doesn't care about units.

For any library that does care about units, I just have to convert from F32 Meters to F32 something. This definitely sounds nicer for people that use measure types.

view this post on Zulip Brendan Hansknecht (Jul 02 2022 at 15:42):

Of course we would need need a way to go from F32 Meters to F32 something. That is the odd part. To my knowledge it would require adding some sort of cast or type variable resetting op to the standard library (maybe specifically for built-in numbers).

Otherwise, as @JanCVanB pointed out, you just get stuck when you want to go from meters per second and seconds to meters.

view this post on Zulip Brian Carroll (Jul 02 2022 at 15:49):

https://package.elm-lang.org/packages/ianmackenzie/elm-units/latest/Quantity#unit-types

The Squared, Cubed, Product and Rate units types allow you to build up and work with composite units in a fairly flexible way.


Last updated: Jun 16 2026 at 16:19 UTC