As i come from Haskell, typeclasses seem crucial for proper polymorphic functions. I read the roc for elm programmers file, and the Num type seems much like Haskell's Num, but all of the instances are just phantoms holding some a
.
@Folkert de Vries or @Ayaz Hafiz are probably most qualified to answer especially in the type theoretical sense, but I think I know the general answer.
Currently, Num a
is special. It is built into the compiler with special restrictions that a Num a
must be either a Float a
or Int a
. Those must then become specific types F32
, I8
, etc.
Currently if I were to make Foo a
that a
could be anything at all. I have no way to constrain it. (well I could make it Foo (Num a)
, but that is just relying on Num a
).
In the future this will be changing. We will be adding abilities. You can think of abilities as java interfaces or rust traits or something of that nature. They enable me to say Foo a where a supports Equality
. Now that a
must be a type that has ==
. This will enable regular users to define something akin to Num a
.
More info on abilities in this chat: #ideas > Abilities
@Thomas Dwyer check out this issue!
very interesting. i have a few comments on the problems and various discussions (perhaps this belongs in a different thread?)
syntax. the qualification of abilities at the end feels weird. I see (Num a) as a preface to the type rather than a clarification at the end. this is a minor gripe with the syntax though. perhaps something like f : a is (Addable, Divisible, Multiplicative) in a, a -> a
. really, i just find it at the end to be weird. maybe it just takes some getting used to.
functionlessness in docs. this is a hard one. the "it wasnt before but now is" could be extended to give examples of usages which are now broken and ways to mitigate them in a seemless fashion. in documentation, there could be a section on function attributes? i imagine on the side there would be stuff like asymptotics and the usual with some other miscellanea, which seems like it would fit right into.
custom arithmetic operators. my comment on this is more a side comment. i think we should avoid an overarching Num ability that requires (+), (-), (*) etc. These should definitely be split into their own components. I say this because many things would like to have the ability to be concatenated, but wouldnt make sense to have a division function or even worse some square root function. there could be ability tags, where one can define Num a : [Addable a, Subtractable a, ...]*
as an alias like how haskell's -XConstraintKinds allows for kind aliases; i kinda like this approach, as it doesnt seem like a bunch of complexity to an existing system, and the syntax is nearly identical to current tag aliases.
functionlessness
oh I need to revise the docs - that was before the Abilities design...functionless is not a thing anymore post-Abilities!
regarding the Num
ability - we talked about that and I think Num
is the way to go.
I agree with James Gosling when he decided not to support operator overloading in Java based on how he saw it being used in C++, and although I want custom numeric types to exist (e.g. a user-defined Fraction
or Complex
type, or numbers with units of measure), I think it's a step in the wrong direction to have +
being used in arbitrary DSLs or overloaded for something like concatenation of strings or other collections.
I think it makes it much clearer that arithmetic operators should only be used for numbers if it's required to implement all the numeric operations in order to use them!
After some thought I'd axe infix + - / *
operators all together!
People are used to write mathematical operations, but this is not strictly math, it's programming which start's pretty low level (you have to choose between I32
and I64
, which is by it self not a math problem)
I think they introduce ambiguity that is hard to get rid off
I think they introduce ambiguity that is hard to get rid off
What specially are you thinking of?
Operating ordering? Other?
Mostly interplay of Number like things that we have built-in, abilities and number like things that people might want to build eg. matrices.
I think the percentage of people who wouldn't use Roc exclusively because it didn't have infix math operators is unacceptably high
If we remove them, we set the expectations. No infix things.
I know that assumption, but I'd like to measure it.
Like, if there is a reason to be hyped for other things about Roc: platforms, purity, sexiness, I mean speed ...
I don't know, I totally get it, but at the same time, people are spoiled.
I get where you're coming from because I've had similar thoughts myself over the years :big_smile:
it would be nice if we had all grown up being taught arithmetic using prefix function calls instead of infix symbols, but unfortunately... :sweat_smile:
you know, if you enumerate all the great thing that language has to offer, and like, yeah you are calling a function to add two numbers, because math operations cost too.
it's the wrong place to spend our weirdness budget
I know. We first publish a book!
Infix Math is Backwards :)
:D
:joy:
I agree, just this whole subject wouldn't exist the axe chopped at that place. :)
Since Roc will have an AST based IDE, couldn't it in theory have a mode where all the infix math is replaced with the underlying add/sub/div/etc function calls?
yep! Definitely possible
Yes
on this topic, i was using roc and was wondering, with no HKTs, how would roc express something akin to Haskell's functor? is it really necessary for each type to define its own map
? it doesnt seem very easy to write generic code to map over something that can be mapped over. functors arent even particularly complicated, but it is very useful. and what about Traversable and Foldable? i know list has List.walk, but what about a generic walk?
the tradeoff we make is to keep the language simple and not have the (inevitable, if you add HKTs) functor/applicative/monad hierarchy. It hurts a bit, but elm proves that it's fine in practice
i understand the simplicity argument, im just confused what the alternative is for this kind of expression. what if i want a function to work on anything mappable? or anything foldable?
the idea is that in practice you don't. List
is effectively the common denominator of data structures, it is what most api's would use. There are particular cases where a Set
or Dict
is useful, they come with a full and specific api (we've had a whole thread on what ordering guarantees such a structure must have. e.g. Set
is not a lawful functor)
wait is it a functor? it's not a monad, I know that
ah here we go https://www.reddit.com/r/haskell/comments/2090x3/ask_rhaskell_why_is_there_no_functor_instance_for/
didnt think about it that way, i guess it makes sense since recursive ADTs are less common in Roc than Haskell, so there is more emphasis on the actual underlying representation rather than an abstract tree of types. given that i can see why its not particularly necessary. im guessing this would also aid in efficiency, since the computer likes arrays more than a node with pointers?
yes absolutely that is one of the major reasons to choose an array as the underlying representation
though to be fair we can only do that because we have opportunistic in-place mutation
I wanted to ask a similar question to this thread from a year ago... if abilities are a language feature in Roc, why not go whole hog on abilities and abstract over kinds?
E.g. I always have this gripe with Rust's stdlib.. what is bind on the effect type I'm working with? It's called "flat_map" on Vec/Iterator, "and_then" on Result, (~await + ?) on Future, and so on. It just feels more organized and ergonomic to unify semantic concepts under canonical terminology.
Definitely understand that the category theoretic hierarchy from Haskell is arcane to the average developer
Someone may have a better answer, but from my view, the question is reversed:
What do we gain from making abilities more complex and adding higher kinded types?
Relatedly, are those gains worth it and do they fit the language well? Those features add to the language complexity, compiler complexity, and increase overall learnability burden.
Features have to earn a place to be worth adding to the language. Abilities happened to pass that threshold due to a few specific use cases (like serialization). As such, they got a minimal implementation to meet those specific use cases.
We want a clean and simple language, so generally we avoid adding features (especially large ones) until there is a compelling reason.
This is a great answer. I've been trying to deduce what the language's design philosophy is outside of the obvious (be functional, embed easily, performance) and this explains it well.
also here's a related FAQ entry! https://github.com/roc-lang/roc/blob/main/FAQ.md#higher-kinded-polymorphism
@Richard Feldman As a veteran of the Scala Wars :tm: I get this
Last updated: Jul 06 2025 at 12:14 UTC