Stream: beginners

Topic: templates?


view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:12):

hi is there any equivalent of c++ template in Roc
I am not saying it should be but just asking

specifically is there a way to do this :

template  <class A>
f(A a  )
{
// do something  with a in generic  way
}

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:15):

No, there is no template system.

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:17):

btw. templates are the reason among many others why I consider C++ piece of garbage, so lack of this ability although annoying at times may prove to be correct design choice, but indeed sometimes I am lacking something : )

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:17):

But you can have generic types. For example List a means a list of any type.

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:17):

how to use that in function definition

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:18):

Can you say more about what you want to do?

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:19):

let say I want to create generic function to add two numbers how to do that ?

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:21):

add : Num a, Num a -> Num a
add = \a, b -> a+b

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:22):

You can only add numbers so it can't work on any type

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:22):

will it work if I pass Float number to it ?

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:22):

Yes

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:23):

Num a stands for something or it is just tag as any in reality ?

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:24):

I mean what to do for below function to be equivalent
add : Numm a, Numm a -> Numm a
add = \a, b -> a+b

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:25):

Doesn't work. Numbers are special.

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:26):

and I can't force Numm to act as Num ?

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:26):

No

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:26):

ok

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:26):

It is built in

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:27):

what is the role fo this "a" added after Num isn't Num just enough if it is build in ?

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:28):

We started with the most complicated example unfortunately!

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:28):

The a is a type variable. It's similar to the typename in the C++ template

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:29):

but Num specifies some restriction on this a ??

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:31):

Yes Roc has builtin number types and all of them are specializations of the generic typeNum a.

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:31):

Numbers are the most complicated, let's talk about lists first for a minute

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:32):

So List a means a list of anything
List User is a list of some type called User
List U8 is a list of unsigned 8 bit integers

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:33):

So List a is a generic type and List U8 is a specialized or concrete type.

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:33):

That is how generic types work

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:34):

can I put List b or "a" is keyword also ?

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:34):

Yes

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:34):

List b or List whatever

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:34):

But you can't change the List and you can't change the Num

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:35):

For numbers, U8 is a type alias for some Num a where I can't remember what the a is

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:35):

Btw different topic ,I have this problem something is returning
-> Task {} *

but I somehow was not able to return
Task.err "something"

view this post on Zulip Brian Carroll (Dec 10 2023 at 22:36):

It's time for bed where I live so maybe someone else can answer that one, and I'd suggest making a new topic

view this post on Zulip Artur Swiderski (Dec 10 2023 at 22:37):

ok thx anyway

view this post on Zulip Anton (Dec 11 2023 at 10:39):

If a function is returning Task {} *, the * means the error part of the Task is empty. With -> Task {} * you could only return Task.ok {} or call a function like Stdout.line "done", which also returns Task {} *.

If you want to return Task.err "something" the signature would need to be -> Task <whatever> Str. <whatever> depends on what you want the function to return on success.

view this post on Zulip Artur Swiderski (Dec 11 2023 at 11:20):

thx for explanation but what * means ? I was thinking it means anything just "*" coincides with anything in my mind : )

view this post on Zulip Anton (Dec 11 2023 at 12:00):

It used to confuse me too :)
We have a brief section on it in the tutorial.
The * means compatible with any type.
For List.len we have the signature len : List * -> Nat because we can pass it a list of Str, U64, ... . To calculate the number of elements in the list, it does not actually matter what an element is.

When you return something with a wildcard type, it may not be immediately clear what that means. Say we have wildcardList : List *, what could we fill in for wildcardList = ...? The only value that can represent a list of any type is the empty list. So the only option is wildcardList = [].

With Task it's very similar. If we have the type Task Str *, we have Str for the success value and * for the error value. Similar to List *, the only error type that could satisfy * is the empty error.

view this post on Zulip Isaac Van Doren (Dec 11 2023 at 13:22):

What’s the reason for using Task a * rather than Task a []?

view this post on Zulip Anton (Dec 11 2023 at 13:30):

Excellent question! I don't know, @Richard Feldman?

view this post on Zulip Fletch Canny (Dec 11 2023 at 15:47):

There's still some details about Roc's type system that I'm hazy on, but wouldn't the difference be that you can't use Task a * when chaining Tasks with a tagged union error type?
Task.await has a type of Task a b, (a -> Task c b) -> Task c b, and if I use Task a [] for Task a b and Task c [A, B, C] for Task c b, the type system can unify [] and[A, B, C]. But if the error type were say, Task a {} then it couldn't unify those types. Since Task a * my be Task a {} that means we can't necessarily unify those types and thus we would get a type error.

view this post on Zulip Brendan Hansknecht (Dec 11 2023 at 15:59):

Give return types are open tags by default now, I believe that is correct.

view this post on Zulip Brendan Hansknecht (Dec 11 2023 at 15:59):

In the old days, that would not be the case

view this post on Zulip Brendan Hansknecht (Dec 11 2023 at 16:03):

That said, even with a closed tag they are slightly different. * has no constructable error case. It suggests that someone has handled all errors and made them into successes.

[] has an error case with no info in it. So it basically means something failed but we have dropped all information about the failure NVM, tired morning brain. Also unconstructable if it is a closed union.

view this post on Zulip Isaac Van Doren (Dec 11 2023 at 16:16):

I don’t see how [] is an error case with no info given that it is impossible to construct an element of []

view this post on Zulip Richard Feldman (Dec 11 2023 at 16:30):

Isaac Van Doren said:

What’s the reason for using Task a * rather than Task a []?

the answer varies depending on how it's being used - where are you seeing the type in question? :big_smile:

view this post on Zulip Isaac Van Doren (Dec 11 2023 at 16:34):

I don’t see how [] is an error case with no info given that it is impossible to construct an element of []

Ah wait I forgot that [] is open

view this post on Zulip Isaac Van Doren (Dec 11 2023 at 16:34):

The first one that comes to mind is Stdout.line

view this post on Zulip Isaac Van Doren (Dec 11 2023 at 16:34):

So Task {} *

view this post on Zulip Brendan Hansknecht (Dec 11 2023 at 17:14):

Yeah sorry, I think Task {} [] and Task {} * are equivalent if [] is a closed union.

view this post on Zulip Brendan Hansknecht (Dec 11 2023 at 17:15):

But given roc makes all returned unions into open unions, they quickly become not equivalent.

view this post on Zulip Richard Feldman (Dec 11 2023 at 17:16):

well also even an open [] is more restrictive than * because [] says "this must be some sort of tag union"

view this post on Zulip Richard Feldman (Dec 11 2023 at 17:16):

and you can have errors that aren't tag unions

view this post on Zulip Brendan Hansknecht (Dec 11 2023 at 17:16):

Definitely problematic to send an opinion union version of Task {} [] back to a platform. It would not have a know layout when generating the platform glue

view this post on Zulip Richard Feldman (Dec 11 2023 at 17:17):

so if Stdout.line returned a Task {} [] then you couldn't use it with a Task {} Str, for example (e.g. if someone used Task.mapErr to turn an error into a string)

view this post on Zulip Artur Swiderski (Dec 11 2023 at 19:05):

ok I can't intellectually follow the discussion : ) but I gather this is the reason why Task {} * is used ?

That said, even with a closed tag they are slightly different. * has no constructable error case. It suggests that someone has handled all errors and made them into successes.

btw what {} stands for here, why specifically this value?

view this post on Zulip Brendan Hansknecht (Dec 11 2023 at 19:16):

{} is just an empty record.

view this post on Zulip Brendan Hansknecht (Dec 11 2023 at 19:17):

It is common used for kinda a unit type. A concrete type that compiles to nothing.

view this post on Zulip Isaac Van Doren (Dec 12 2023 at 03:28):

and you can have errors that aren't tag unions

Aren't there no possible errors for a Task {} *?

Regardless, isn't there not much you can do with errors if you don't know what type they are?

view this post on Zulip Brendan Hansknecht (Dec 12 2023 at 03:40):

Aren't there no possible errors for a Task {} *?

This is were it gets confusing depend on how things are worded. Really easy to say something that is easily misinterpreted from what was meant. You are correct that there are no possible constructable errors that match the type Task {} *.

Richard meant that Task {} * will unify with any error type. That means I can do:
Note: Stdout.line is a Task {} *. I has a * error case but merges to a Str error case here just fine.

main =
    errStr <- strErrTask |> Task.onErr
    Stdout.line "error was: \(errStr)"

strErrTask =
    _ <- Stdout.line "Some text" |> Task.await
    Task.err "This is a string err"

If I were to redefine Stdout.line to be Task {} [], I would get this error:

── TYPE MISMATCH ───────────────────────────────────── examples/helloWorld.roc ─

This 2nd argument to |> has an unexpected type:

11│>      _ <- Stdout.line "Some text" |> Task.await
12│>      Task.err "This is a string err"

The argument is an anonymous function of type:

    {} -> Task * Str

But |> needs its 2nd argument to be:

    {} -> Task * []

view this post on Zulip Brendan Hansknecht (Dec 12 2023 at 03:41):

Task {} [] can only unify with other errors that are tags.

view this post on Zulip Isaac Van Doren (Dec 12 2023 at 04:52):

Aha! That makes sense. Thanks for the detailed explanation!


Last updated: Jul 06 2025 at 12:14 UTC