Stream: ideas

Topic: Unbound type variable naming conventions


view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:17):

Type variables in many ML derived languages use a single letter from an alphabetic sequence and don't convey any meaning about them to the user. Many beginners struggle with this, and even experienced programmers find them less understandable in long type signatures.

i propose that in the Roc standard library, official packages and platforms, and documentation we use and promote the use of semantically meaningful type variable names

So List(item) over List(a)
And Map(key, value) over Map(a, b) or even Map(k, v)

Etc

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:19):

regarding meaningful type var names - for years I did this in Elm and I honestly have mixed feelings about it in retrospect

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:19):

in Elm I would write things this all the time:

cancelButton : Html msg
cancelButton = button [] [ text "Ok"  ]

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:19):

instead of this:

cancelButton : Html a
cancelButton = button [] [ text "Ok"  ]

view this post on Zulip Kiryl Dziamura (Jul 03 2025 at 14:21):

I really like the idea of meaningful names in std.

How do you think this would look like?

map2 :
    Result a err,
    Result b err,
    (a, b -> c)
    -> Result c err

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:21):

one downside I encountered in practice: when talking about this out loud (e.g. in meetups) I'd always be saying "HTML msg with a little M" because if I just said "HTML msg" they wouldn't know what I was talking about. "HTML a" doesn't have that problem. (Neither does "HTML star" which was one of my motivations for trying out *.)

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:23):

I would say HTML for some message

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:23):

another consideration:

List.map : List(elem_a), (elem_a -> elem_b) -> List(elem_b)
Result.map : Result(ok_a, err), (ok_a -> ok_b) -> Result(ok_b, err)
List.map : List(elem1), (elem1 -> elem2) -> List(elem2)
Result.map : Result(ok1, err), (ok1 -> ok2) -> Result(ok2, err)
List.map : List(a), (a -> b) -> List(b)
Result.map : Result(a, err), (a -> b) -> Result(b, err)

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:24):

I use this example because here there's tension between consistent naming for container elements and consistent naming across maps

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:25):

the last example is consistently using a and b across map functions

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:25):

the first two are also using a consistent convention (_a/_b suffix or 1/2 suffix) but they're a lot longer to read

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:25):

I feel like

List.map : List(src), (src -> dest) -> List(dest)
Result.map : Result(src, err), src -> dest) -> Result(dest, err)

is more logical than using numbers or letters

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:26):

yeah, or before/after

view this post on Zulip Kiryl Dziamura (Jul 03 2025 at 14:26):

#ideas > Unbound type variable naming conventions @ 💬

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:26):

src/dest is cool, I'd never thought about those!

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:26):

Yeah, just something to tell the reader of the type what we are doing with those types

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:27):

Elm does this, but not for the core data structures

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:27):

map_both :
    Result(src1, err),
    Result(src2, err),
    (src1, src2 -> dest)
    -> Result(dest, err)

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:27):

that looks pretty nice to me

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:27):

Yeah me too

view this post on Zulip Richard Feldman (Jul 03 2025 at 14:27):

better than

map_both :
    Result(a, err),
    Result(a, err),
    (a, b -> c)
    -> Result(c, err)

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:28):

Yep

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:28):

I think thinking about the type vars should be put under the same scrutiny as every other part of a type or functions api

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:29):

Like what does this function do?

Task(x, a) -> Task(y, Id)

if you didn't write the book on Elm? :wink:

view this post on Zulip Kiryl Dziamura (Jul 03 2025 at 14:30):

I think in each case it depends on the function itself. Like, map_both and map give the context for other names

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:31):

yep, which means you can communicate via types much more easily, and also DESIGN with types with more clarity

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:33):

Task(error, value) := [Task(error, value)]

is easy to understand and i don't even have to read the docs to get the basic idea

view this post on Zulip Anthony Bullard (Jul 03 2025 at 14:34):

as opposed to Task(x, a) as Elm does today

view this post on Zulip Richard Feldman (Jul 03 2025 at 15:30):

Anthony Bullard said:

Like what does this function do?

Task x a -> Task y Id

if you didn't write the book on Elm? :wink:

amusingly, in Elm, I can tell just from the type that it returns Task.fail someHardcodedId and anything else it does before that is just heating up the CPU at best or crashing/infinite looping at worst :laughing:

view this post on Zulip Richard Feldman (Jul 03 2025 at 15:31):

but yes, in general I agree - thinking harder about type variable names is good!

view this post on Zulip Richard Feldman (Jul 03 2025 at 15:41):

what would be a good one for Num?

view this post on Zulip Richard Feldman (Jul 03 2025 at 15:41):

like what would be better than a here?

answer : Num(a)
answer = 42

view this post on Zulip Richard Feldman (Jul 03 2025 at 15:42):

elem is an obvious choice for List, but I'm not sure what it would be for Num

view this post on Zulip Kiryl Dziamura (Jul 03 2025 at 15:43):

for me, it depends on context. is Num(a) a thing on it's own?

view this post on Zulip Kiryl Dziamura (Jul 03 2025 at 15:44):

but value seems to be valid by default.
upd. probably weird
Num.add : Num(value), Num(value) -> Num(value)

view this post on Zulip Richard Feldman (Jul 03 2025 at 16:06):

but I think for collections it describes what's being contained, e.g.

List(elem)
Dict(key, val)
Result(ok, err)
Num(value)

the last one feels out of place to me, because really the type variable is describing the precision of the number - e.g. is it an integer or frac, how many bits, float vs decimal, etc.

view this post on Zulip Richard Feldman (Jul 03 2025 at 16:06):

Num(precision) is too long though, and Num(prec) doesn't feel very self-descriptive

view this post on Zulip Richard Feldman (Jul 03 2025 at 16:06):

could be like Num(size) I guess?

view this post on Zulip Kiryl Dziamura (Jul 03 2025 at 16:11):

ah, indeed. I forgot Num is a container around other types. I was thinking about smth like I8(value). and that's another example of how meaningful var names can help. size helps me to remember the fact that I'm not manipulating a number, but a number of a certain size

view this post on Zulip Richard Feldman (Jul 03 2025 at 16:12):

Num(range) is another contender

view this post on Zulip Richard Feldman (Jul 03 2025 at 16:12):

since it's like "a number of a certain range" although that gets a little weird when you get into the fact that floating-point numbers lose precision outside certain ranges

view this post on Zulip Richard Feldman (Jul 03 2025 at 16:13):

I like size a bit better, not sure why though

view this post on Zulip Kiryl Dziamura (Jul 03 2025 at 16:13):

because usize :)

view this post on Zulip Richard Feldman (Jul 03 2025 at 16:14):

I think maybe because range kind of suggests to me that you can control the exact range, like "oh I want this number to be between exactly 42 to 76"

view this post on Zulip Kiryl Dziamura (Jul 03 2025 at 16:20):

Yes, that's what I thought as well

view this post on Zulip Kiryl Dziamura (Jul 03 2025 at 16:21):

I just checked wikipedia, https://en.wikipedia.org/wiki/Integer_(computer_science)?wprov=sfti1#

size is everywhere there. Very natural term

view this post on Zulip Anthony Bullard (Jul 03 2025 at 16:34):

size or precision i would think

view this post on Zulip Kilian Vounckx (Jul 04 2025 at 04:54):

Richard Feldman said:

yeah, or before/after

I've never thought about it, but now I can't unsee it. Why are 'before' and 'after' alphabetically opposite to 'a' and 'b'??

But yeah for some on-topic feedback. I prefer slightly descriptive type variable names as well. I feel like one of the reasons they aren't in haskell is because it's harder to do for the more abstract higher-kinded type variables. Also math

view this post on Zulip Anton (Jul 04 2025 at 09:12):

I like descriptive type variables as well, my current preference goes to the numbered style ok1. We don't use numbers elsewhere so it's an easy to way to spot a type variable.

view this post on Zulip Anton (Jul 04 2025 at 09:30):

Perhaps something with in and out could work too. src and dest make me think of other things.

view this post on Zulip Kiryl Dziamura (Jul 04 2025 at 09:32):

from/to also


Last updated: Jun 16 2026 at 16:19 UTC