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
regarding meaningful type var names - for years I did this in Elm and I honestly have mixed feelings about it in retrospect
in Elm I would write things this all the time:
cancelButton : Html msg
cancelButton = button [] [ text "Ok" ]
instead of this:
cancelButton : Html a
cancelButton = button [] [ text "Ok" ]
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
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 *.)
I would say HTML for some message
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)
I use this example because here there's tension between consistent naming for container elements and consistent naming across maps
the last example is consistently using a and b across map functions
the first two are also using a consistent convention (_a/_b suffix or 1/2 suffix) but they're a lot longer to read
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
yeah, or before/after
src/dest is cool, I'd never thought about those!
Yeah, just something to tell the reader of the type what we are doing with those types
Elm does this, but not for the core data structures
map_both :
Result(src1, err),
Result(src2, err),
(src1, src2 -> dest)
-> Result(dest, err)
that looks pretty nice to me
Yeah me too
better than
map_both :
Result(a, err),
Result(a, err),
(a, b -> c)
-> Result(c, err)
Yep
I think thinking about the type vars should be put under the same scrutiny as every other part of a type or functions api
Like what does this function do?
Task(x, a) -> Task(y, Id)
if you didn't write the book on Elm? :wink:
I think in each case it depends on the function itself. Like, map_both and map give the context for other names
yep, which means you can communicate via types much more easily, and also DESIGN with types with more clarity
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
as opposed to Task(x, a) as Elm does today
Anthony Bullard said:
Like what does this function do?
Task x a -> Task y Idif 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:
but yes, in general I agree - thinking harder about type variable names is good!
what would be a good one for Num?
like what would be better than a here?
answer : Num(a)
answer = 42
elem is an obvious choice for List, but I'm not sure what it would be for Num
for me, it depends on context. is Num(a) a thing on it's own?
but value seems to be valid by default.
upd. probably weird
Num.add : Num(value), Num(value) -> Num(value)
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.
Num(precision) is too long though, and Num(prec) doesn't feel very self-descriptive
could be like Num(size) I guess?
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
Num(range) is another contender
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
I like size a bit better, not sure why though
because usize :)
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"
Yes, that's what I thought as well
I just checked wikipedia, https://en.wikipedia.org/wiki/Integer_(computer_science)?wprov=sfti1#
size is everywhere there. Very natural term
size or precision i would think
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
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.
Perhaps something with in and out could work too. src and dest make me think of other things.
from/to also
Last updated: Jun 16 2026 at 16:19 UTC