Stream: ideas

Topic: syntax sugar for units of measure


view this post on Zulip Karl (Jan 02 2025 at 19:16):

The numeric postfix fn thing is fun. I presume it'd be restricted to Frac|Int -> T signatures. I request that the type coercion functions are named u8, i64, etc instead of the to_...mentioned in the video.

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:35):

We would definitely need to either have 12h desugar to 12.to_h(), or change Num.to_u8() and similar functions to Num.u8(). The latter option seems more confusing, we should probably keep the to_* prefix

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:36):

the idea is for 12h to desugar to h(12)

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:37):

so you can bring your own functions rather than being limited only to functions that are defined on Num already

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:37):

e.g. if a graphics platform wants to use 12px

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:37):

or a scientific computing one wants to do 12light_years or something

view this post on Zulip Karl (Jan 02 2025 at 19:37):

12USD

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:38):

The problem is that 12u8 is easily readable, but the function name makes less sense outside of suffix usage

view this post on Zulip Karl (Jan 02 2025 at 19:38):

I'd like to still type 12u64 though

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:38):

Because now Num.to_u8 is Num.u8

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:39):

Karl said:

I'd like to still type 12u64 though

I don't think we can reasonably have both

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:40):

12.to_u8() or 12.u8() would be fine alternatives to 12u8 but if we're having 12h and 12u8 coexisting and meaning totally different things, that seems like a mistake

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:41):

I thought the suggestion in the video was for 12u8 to now mean 12.to_u8() where 12 is Num *

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:41):

no, the other way around

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:41):

the idea would be that 12x now means x(12) always, unambiguously

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:41):

Okay

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:41):

which in turn means that 12u8 would mean u8(12)

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:41):

yep

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:41):

which in turn means we can't have that be the syntax anymore

view this post on Zulip Karl (Jan 02 2025 at 19:42):

Having u8 and friends as free functions isn't an option?

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:42):

but if we have Num.to_u8 : Num a -> U8 then we can just have 12.to_u8()

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:42):

I'd assumed Num would expose [u8, u16, etc.]

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:42):

also fine

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:43):

oh you mean like u8 and u16 are always imported unqualified by default in every module?

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:43):

Yes, 12u8 can't be a syntax for numeric literals

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:43):

not sure how much of a problem that would be

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:43):

I'd rather not do that; currently we don't expose any values unqualified

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:43):

I really don't think 12.u8() is a problem haha

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:43):

Besides the plan to do that for True, False, Ok, Err

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:44):

true haha

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:44):

I guess those are different because they're tag names rather than variable names

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:44):

It's fine, yep. Just pushing for consistent naming in the case we go with 12u8 means 12.u8() and 12h means h(12)

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:44):

like we have variable names of u16 and such all the time

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:45):

Yeah, it'd be a problem for any decoding impls

view this post on Zulip Notification Bot (Jan 02 2025 at 19:45):

34 messages were moved here from #show and tell > roc-realworld initial exploration by Richard Feldman.

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:46):

this might be a terrible idea, but maybe allowing like 1px + (x - 3)px could be a thing

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:47):

a downside would be that at that point, ("a string")str_fn becomes valid :stuck_out_tongue:

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:47):

well, or maybe it's an upside somehow, but it looks strange :sweat_smile:

view this post on Zulip Sam Mohr (Jan 02 2025 at 19:47):

No, it's probably a downside haha

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 19:48):

given number types will unify automatically 27u8 doesn't feel too important as much as it feels proper.

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:50):

we can special-case the method calls to do that though

view this post on Zulip Richard Feldman (Jan 02 2025 at 19:50):

like we can special-case 42.u8() behind the scenes the same way we special-case 42u8 today

view this post on Zulip Agus Zubiaga (Jan 02 2025 at 23:56):

404respond :stuck_out_tongue_wink:

view this post on Zulip Sam Mohr (Jan 02 2025 at 23:57):

How do we ban Agus /s

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:01):

I'm not going to yell, but I'll register politely that I really don't like this.

view this post on Zulip Richard Feldman (Jan 03 2025 at 00:02):

the whole idea, or just using it in silly ways?

view this post on Zulip Brendan Hansknecht (Jan 03 2025 at 00:02):

In a paren's and comma world does this syntax have an advantage over just doing px(27)?

Like just really minor looks, right? Not really less verbose or better for any specific use.

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 00:02):

I think the main downside is that you’d get a really confusing error if you forget a space somewhere after a number

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:03):

Like what is this px function doing?

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:03):

We should be able to just add a CalledVia variant for UnitSuffix

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:03):

Creating a Custom Type?

view this post on Zulip Brendan Hansknecht (Jan 03 2025 at 00:03):

Yeah, maybe also converting units. At least that is the expected use.

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:04):

I'd rather see Px(1) + Px(x - 3)

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 00:04):

Something like https://github.com/Hasnep/roc-units I’d imagine

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:04):

or the lowercase equivalent

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:05):

I'd imagine you'd just expose the units you want with the nice abbreviations you like and then just call them

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:06):

Not to be too dramatic, but I think this is like Perl/Raku-level magic

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:07):

And the syntax is only for literals? Or literals and parenthesized expressions?

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:07):

It's a little weird if you don't know what's happening, but it's just a function call of the suffix on the number. Seems really simple if you know what's happening

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:07):

We couldn't do xpx where x is the var and px is the unit func, so no

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:08):

Unless we allow (<expr>)suffix

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:08):

I'm not going to die on the hill, but it seems little feature-cluttery to make a syntactic sugar for calling a function on a number literal

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:08):

Which would open the door to postfix function calls, probably not a good idea

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:09):

I'd like to see examples of abuse of this feature

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:10):

I've seen it used correctly, but I don't look at much code in languages where this exists

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 00:10):

1exit

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:11):

LOLOLOLOLOLOLOLOL

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:11):

You're very creative lmao

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:12):

I think you'd make a good play tester :wink:

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:12):

I think you could make a great package that parses expressions like this in Roc :smile:

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:14):

expect Units.parse("1px + (x - 3)px", Map.empty().set("x", 15)) ?? Px(0) == Px(13)

view this post on Zulip Richard Feldman (Jan 03 2025 at 00:15):

the most generalized version of it would be saying you can call functions in either prefix or postfix style:

fn(arg1, arg2)
(arg1, arg2)fn

view this post on Zulip Richard Feldman (Jan 03 2025 at 00:16):

WWRD (What Would Raku Do?)

view this post on Zulip Anthony Bullard (Jan 03 2025 at 00:17):

Richard Feldman said:

WWRD (What Would Raku Do?)

Be irrelevant outside of memes and Perl hardcores

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:18):

Flame war incoming

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 00:48):

Karl said:

12USD

It can even support the Cape Verdean escudo where they use the currency symbol as the decimal separator:

esc = \i -> \d -> Escudo (i + d / 100)

2esc(50)

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 00:50):

I'll show myself out

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:50):

@Agus Zubiaga 3 out of 6 messages in this topic from you are shitposts. I'm impressed

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 00:52):

You were asking for ways to abuse it

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:52):

I didn't know I was talking to a real-life genie

view this post on Zulip Sam Mohr (Jan 03 2025 at 00:56):

That said, these were useful examples to consider. Thank you

view this post on Zulip Karl (Jan 03 2025 at 01:11):

As for another language where something like this exists, Ruby (with a dot so 2.hours) is the obvious example with people monkeypatching their DSLs.

Nim loves syntax sugar and has optional paren free calls along with the x.foo(2) -> foo(x, 2) swap which does work on number literals so it can do 2.hours as well. I don't recall any specific abuse but I haven't spent a ton of time in the Nim community and there are much shinier toys for those inclined that way.

I believe it could be done with a reader macro in lisps that support them but I was always in the Scheme/Clojure subset which are reader-macro free.

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 01:13):

Jokes aside, this doesn’t really seem to be a footgun. You can always abuse features to write terrible code, but I don’t think this encourages that.

view this post on Zulip Karl (Jan 03 2025 at 01:15):

I forgot! F# has an entire units of measure feature which looks like 12<cm>. I've seen that abused for currency which is why I did the 12USD above.

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 01:16):

Why do you consider currency an abuse?

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 01:17):

I don’t think it’s a stretch to call those a unit

view this post on Zulip Karl (Jan 03 2025 at 01:17):

The system is very much intended to be for units of measure. You can express relations between units, express 30<m/s> and so on. Currency isn't in the spirit of the system as a whole.

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 01:17):

I do struggle to come up with an actual use for it, though

view this post on Zulip Karl (Jan 03 2025 at 01:18):

I don't think it caused any problems though.

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:18):

Karl said:

Currency isn't in the spirit of the system as a whole.

what's the downside of that though? :thinking:

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:18):

like if it's not error-prone and people like it, what's the problem?

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:20):

Karl said:

As for another language where something like this exists, Ruby (with a dot so 2.hours) is the obvious example with people monkeypatching their DSLs.

this was specifically what I was thinking of - I think this is one of the things people find delightful about Ruby, and it's something I miss about programming in it

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:20):

monkeypatching is not the way I'd want to get it, but an alternate function call syntax seems totally harmless compared to monkeypatching

view this post on Zulip Karl (Jan 03 2025 at 01:20):

I'm not going to condemn anybody for not using something in the way that's intended but I still consider it an abuse. I'm way further into the syntax sugar/metaprogramming spectrum than you all seem to be so I think this is fun.

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:21):

and I don't see people doing 1.exit in Ruby even though they could :big_smile:

view this post on Zulip Brendan Hansknecht (Jan 03 2025 at 01:31):

Well I doubt this would get abused (much), is it really enough of a gain to be worth using over px(27)?

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:34):

it's totally fine to just use px(27) or h(12) or whatever (and I don't feel that strongly about the idea), I just find now + 12h more delightful than now + h(12) or now + hours(12) :shrug:

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:34):

and very straightforward to explain etc.

view this post on Zulip Brendan Hansknecht (Jan 03 2025 at 01:34):

Fair enough.

view this post on Zulip Brendan Hansknecht (Jan 03 2025 at 01:35):

I think if we do this, it should only be for it literals and not expressions.

view this post on Zulip Brendan Hansknecht (Jan 03 2025 at 01:35):

and numeric literals at that

view this post on Zulip Agus Zubiaga (Jan 03 2025 at 01:37):

100pct

view this post on Zulip Anthony Bullard (Jan 03 2025 at 01:43):

I think Roc is best served by having the smallest set of primitives and any new one should have a STRONG justification

view this post on Zulip Anthony Bullard (Jan 03 2025 at 01:44):

This doesn’t meet the bar for me

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:44):

when I think about primitives, I'm usually thinking of things that have different semantics

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:44):

not so much syntax sugar

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:45):

although of course it is possible to go overboard with sugar, and more sugar also does carry the inherent downside of there being more than one way to do something

view this post on Zulip Richard Feldman (Jan 03 2025 at 01:45):

so there are definitely tradeoffs

view this post on Zulip Anthony Bullard (Jan 03 2025 at 02:13):

Maybe I don’t live in the problem domains where this gives a big lift

view this post on Zulip Anthony Bullard (Jan 03 2025 at 02:14):

And I’ve only seen this in languages with one or both of duck typed objects or getters/setters

view this post on Zulip Anthony Bullard (Jan 03 2025 at 02:15):

Except F#. And I’ve never used that feature there or how it works

view this post on Zulip Anthony Bullard (Jan 03 2025 at 02:18):

Here’s an article on it https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/units-of-measure

view this post on Zulip Brendan Hansknecht (Jan 03 2025 at 02:18):

I feel like this wouldn't be that useful without proper unit support such that it can understand equations.

So you can do ft/s to to a feet per second variable.

In the current form suggested by this thread, it really can only unify to a single representation and do addition. Can't do multiplication or division which is fundamental for it to be useful for physics for example.

view this post on Zulip Anthony Bullard (Jan 03 2025 at 02:20):

F# uses type wrappers and sugar that defines the relationship of units to one another

view this post on Zulip Anthony Bullard (Jan 03 2025 at 02:21):

So not functions at all. Seems like they probably generate some methods and operator overloads for these types

view this post on Zulip Sam Mohr (Jan 03 2025 at 02:21):

In particular, I think you mean that we don't have heterogenous type overloading for math operations

view this post on Zulip Sam Mohr (Jan 03 2025 at 02:22):

You being Brendan

view this post on Zulip Sam Mohr (Jan 03 2025 at 02:22):

So you can only implement multiplication for one input (the custom type) and one output (maybe the custom type)

view this post on Zulip Anthony Bullard (Jan 03 2025 at 02:24):

Yeah we can’t define add : Cm(I64), Ml(I64) -> Cm(I64) AND add : Cm(I64), Cm(I64)-> Cm(I64) , etc…

view this post on Zulip Anthony Bullard (Jan 03 2025 at 02:24):

In the same module

view this post on Zulip Brendan Hansknecht (Jan 03 2025 at 02:35):

we don't have heterogenous type overloading for math operations

yeah

view this post on Zulip Kilian Vounckx (Jan 03 2025 at 08:21):

C++ has something similar. I don't know how to define it there (is it a normal function, a class wrapper, a template, who knows...). But I've used std definitions of it. The two main ones are defining a string_view literal "hello"sv. But this is not a use case in roc.
However, the chrono header has a lot of these so you can define durations just like 200ms or 200s. This is a lot more ergonomic than rust's Duration::from_seconds. (Not speaking about all the things rust does better though :sweat_smile:)


Last updated: Jun 16 2026 at 16:19 UTC