In reply to this message. In List _
, _
is a type hole, the compiler will fill this in itself using type inference. You could use List _
as follows:
strList : List _
strList = ["abc", "def"]
numList : List _
numList = [1, 2, 3]
If we use List *
we do define a type ourselves; e.g. lst : List *
could only be lst = []
.
So the *
is somewhat similar to the void type in other languages? So one may prevent of using other types, like in combination with the Task
type with Task Str *
to say that there is no effect allowed in the function?
So the
*
is somewhat similar to the void type in other languages?
In some cases (like Task Str *
), it is similar, but when it is used for a function argument e.g. List.isEmpty : List * -> Bool
, it is not like void at all because here it means a list of any type (List Str
, List U64
...).
It still is functionally a void type there. You have a list of what might as well be void cause you can't interact with the elements
You still can get the length though
Cause that doesn't depend on the element type
Thank you, I think I'm getting it now. At first i thought that both, the _
and the *
were some sort of generics placeholders. But after some tinkering it seems that the _
is really only a placeholder for the type the compiler will figure out and fill in, which may be a generic type and even the *
if needed. That's why it is called type hole. But the *
is actually a generic type, but says more specific to the reader that the type is ignored and not used, at least in the case of a container type like List *
, where one wants to operate on the list itself but without using the actual elements, like for example when calculating the list's size as here the actual element's type doesn't matter. With a List a
one might think that the type is important whereas in List *
it is clear that the element's type isn't used and therefore doesn't matter. So it is more explicit. So the *
can be seen as something like any
or object
types in other languages I think.
That is a good summary with the caveat that any
and object
types generally have some form of runtime reflection that can allow you to convert them to other types and interact with them. Roc has no such infrastructure. You simply can't interact with an element type that is *
.
Thanks for the clarification. But I must say that I see such type definitions more like a contract. So even in languages like C# or Java, when a method consumes an interface type object or returns an interface type object, what I care about is the functionality that the interface offers me. But yeah, I've seen a lot of code from other people like my coworkers who cast the interface type objects to other types because they know which type it really is so that they are able to call methods not provided by interface. And when the concrete type gets swapped out everything crashes at runtime and we have to fix. A really awful place to be in, so I tend to see types more like a contract which allow me to do something with the object behind. If the given type does not offer the requested operations, then I have to refactor something.
But then I think the question has been answered, so I'll mark the topic as resolved. Thank you guys!
Akeshihiro has marked this topic as resolved.
And when the concrete type gets swapped out everything crashes at runtime and we have to fix.
Yeah, have definitely seen that before.
Last updated: Jul 06 2025 at 12:14 UTC