Hi, just a bit of feedback on the wildcard types part of the tutorial (https://www.roc-lang.org/tutorial#wildcard-type).
Type variables have been easy to understand because I just mapped them to Rust's generics. I've been confused of why a wildcard was needed in isEmpty : List * -> Bool
because I'm naturally excepting isEmpty : List a -> Bool
to work just fine. Which makes me wonder why the wildcard type exist.
The type of List.isEmpty actually is List a -> Bool. I also checked the roc codebase, examples and basic-cli and we never use List *
in any roc file. This also makes me wonder why we have wildcard types :p
Maybe it's for type inference, when you use an empty list somewhere without providing a type annotation.
If there's only one type variable in your signature then it makes no difference whether you use *
or a
But consider these signatures
List a, List a -> Bool
List a, List b -> Bool
List *, List * -> Bool
1 and 2 are different signatures. In signature 1, the two lists must contain the same type of value. In signature 2 that is not a constraint. They can be different or the same.
Signatures 2 and 3 are the same.
Signature 3 is just a convenience notation for signature 2.
oh alright, got it now, thank you very much !
Maybe it's a concept that could be approached later in the tutorial ? At least, after the variable types part, as it seems mainly used for type inference and not much in day to day development.
Let me know if you need help updating that part.
I wonder if we should cut the wildcard types
section :thinking:
perhaps it does come up often enough in the repl or in error messages...
We could also move it to the examples repo
I think the main place it comes up is the standard library documentation.
Probably worth a mention in a subsection at the end of the type variable section. Just something short, maybe even brians example
I think the main place it comes up is the standard library documentation.
The standard library documentation is all in roc files right? And I searched for List *
in all roc files in the repo and got nothing, so seems like it's not in the documentation either.
I think since it's in the std lib it should be mentioned in the tutorial. It's true though that in application code you don't often write *
, it's mostly for library code. So a beginner intro should cover how to read it at least.
Oh!
I was still focused on List *
, it does come up regularly for Dict, Set, and when using Task.
I've changed List isEmpty
, withCapacity
and len
to use a wildcard for consistency with Dict and Set.
Is it something that could be removed from the language ? Inferred signatures could have default variable type names such as a
, b
, c
, ..
, aa
. As a beginner not used to FP notations, it seems clearer to not have both. But I may be missing important uses cases.
It could be replaced, but seeing List a
is meant to have a meaning. a
is a type variable that is expected to be matched with other locations. By writing a
it is kinda like specifying that you care about the type.
(List a, a)
means that I care about the element type of the list. It must match the other value in the tuple.
Seeing (List a, b, List c, d)
is just noise. None of those type variable have any meaning. As a reader of the type signiture, I expect to see the type variables used elsewhere, but they aren't.
Seeing a *
is a clear signal that the type can not matter. There is no way it can be depended on or matched at all.
Indeed, the nuance was unclear to me, but makes perfect sense now. Thanks for sharing !
Last updated: Jul 06 2025 at 12:14 UTC