Stream: beginners

Topic: static dispatch on a branch of the union


view this post on Zulip gavr (Apr 09 2026 at 13:37):

I have this code, and my question is, why User.welcome(guest) works when guest.welcome() is not allowed?
What is the type of guest here? My current assumption is that User is a type, and LoggedIn, Guest are constructors of this type(like it would be in Gleam).
But the fact that this example doesn't work makes me think that branches are real types with some kind of sub typing from User.

app [main!] { pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.6/2BfGn4M9uWJNhDVeMghGeXNVDFijMfPsmmVeo6M4QjKX.tar.zst" }

import pf.Stdout
import pf.Stdin


# 2 tagged union
User := [
  LoggedIn({name: Str}),
  Guest
].{
  welcome = |self| match (self) {
    Guest => dbg "Welcome Guest"
    LoggedIn(name) => dbg "Welcome Guest".concat(name)
  }
}

main! = |_a| {

  guest = Guest()
  User.welcome(guest)
  guest.welcome() // ERROR

  Ok({})
}

UPD
This works:

  # guest : User
  guest = Guest()
  User.welcome(guest)

and that:

  guest : User
  guest = Guest()
  User.welcome(guest)
-- TYPE MISMATCH ---------------------------------

The first argument being passed to this function has the wrong type:
   ┌─ main.roc:35:3
   │
35 │   User.welcome(guest)
   │                ^^^^^

This argument has the type:

    User

But main.User.welcome needs the first argument to be:

    [Guest, LoggedIn(Str)]

Which really means that hmm... that Guest + LoggedIn != User?

view this post on Zulip gavr (Apr 09 2026 at 13:43):

Question 2 kinda. When Im trying to add type for

  welcome = |self| match (self) {
    Guest => dbg "Welcome Guest"
    LoggedIn(name) => dbg "Welcome Guest".concat(name)
  }

welcome : User -> {}

It has the type:

    User => {}

But the annotation say it should be:

    User -> {}

Hint: This function is effectful, but a pure function is expected.

But

The method concat has the type:
    Str, Str -> Str

Which means dbg makes functions dirty? I was thinking that using dbg instead of stdout print is the main reason to "cheat", like dbg statemets will be erased in release mode so functions stays pure, just like in Nim.

And when I change type to -> Str, and remove dbg

  welcome : User => Str
  welcome = |self| match (self) {
    Guest =>  "Welcome Guest"
    LoggedIn(name) =>  "Welcome Guest".concat(name)
  }

Im getting

-- TYPE MISMATCH ---------------------------------

The concat method on Str has an incompatible type:
   ┌─ main.roc:25:24
   │
25 │     LoggedIn(name) =>  "Welcome Guest".concat(name)
   │                        ^^^^^^^^^^^^^^^

The method concat has the type:

    Str, Str -> Str

But I need it to have the type:

    Str, { name: Str } -> _ret

What does it means?

view this post on Zulip Jared Ramirez (Apr 09 2026 at 13:59):

hey there! thanks for the detailed write-up, I can answer both questions!

view this post on Zulip Jared Ramirez (Apr 09 2026 at 14:06):

for question 2, it needs to be:

 LoggedIn(user) =>  "Welcome Guest".concat(user.name)

view this post on Zulip Jared Ramirez (Apr 09 2026 at 14:07):

in the original version it has LoggedIn(name) => "Welcome Guest".concat(name), which is trying to concat a Str and the payload of LoggedIn, which is {name : Str}

view this post on Zulip Jared Ramirez (Apr 09 2026 at 14:08):

I'm not sure why dbg is making the function effectful, that seems like a bug

view this post on Zulip Jared Ramirez (Apr 09 2026 at 14:20):

for question 1:

the root cause is the same as question 2! the error messages says:

This argument has the type:

    User

But main.User.welcome needs the first argument to be:

    [Guest, LoggedIn(Str)]

which is correct, because User.LoggedIn has the type: LoggedIn({name: Str})

view this post on Zulip Jared Ramirez (Apr 09 2026 at 14:21):

If you update the match statement for the logged-in user to match what I have above, your original example type checks:

guest = User.Guest # also, the () is not needed here
User.welcome(guest)
guest.welcome()

view this post on Zulip Jared Ramirez (Apr 09 2026 at 14:28):

I think there's definitely an opportunity here to improve the error message in this case, though, to highlight that the payloads of LoggedIn do not match.

view this post on Zulip Jonathan (Apr 09 2026 at 14:29):

It took me a couple reads to see what the problem was there - this seems like a great case for a compiler hint? Something like

guest.welcome()
      ^^^^^^^
hint: The function `welcome` was found in the module `User`, but its
 first argument does not accept `User` so cannot be used as a method

view this post on Zulip Anton (Apr 09 2026 at 15:22):

I will make some issues :)

view this post on Zulip Anton (Apr 09 2026 at 15:35):

#9328

view this post on Zulip Anton (Apr 09 2026 at 15:49):

#9329

view this post on Zulip gavr (Apr 09 2026 at 20:29):

Thanks! I was thinking that match destructs the struct inside, so I can do smth like
LoggedIn(name, age)
since I have background of langs with tagged unions in which fields can be attached directly to .. tag, not single opaque struct that need to be destructed.

view this post on Zulip gavr (Apr 09 2026 at 20:39):

Hmm, I dont get it, maybe another opportunity for better error :)
There is no error in welcome so I guess the destructing of name is correct.
The error is on the call side. (and Ik that {} means () Unit)

User := [
  LoggedIn({name: Str}),
  Guest
].{
  # 10 switch
  welcome : User => Str
  welcome = |self| match (self) {
    Guest =>  "Welcome Guest"
    LoggedIn({name}) =>  "Welcome Guest".concat(name)
  }
}

main! = |_a| {
  person: Person
  person = {name: "String", age: 24}
  p2 = person->have_birthday()
  dbg p2
  guest : User
  guest = Guest()
  User.welcome(guest)
  # guest.welcome()

  Ok({})
}
 roc main.roc                                                                                                           1 master  ✱
-- TYPE MISMATCH ---------------------------------

This expression produces a value, but it's not being used:
   ┌─ main.roc:36:3

36 │   User.welcome(guest)
   │   ^^^^^^^^^^^^^^^^^^^

It has the type:

    Str

Since this expression is used as a statement, it must evaluate to {}.
If you don't need the value, you can ignore it with _ =.

Found 1 error(s) and 0 warning(s) for main.roc.

If I comment out the #User.welcome(guest) Im getting only a warning about unused guest(not an error!)

~/D/F/L/Roc ❯❯❯ roc main.roc                                                                                                           1 master  ✱
-- UNUSED VARIABLE -------------------------------

Variable guest is not used anywhere in your code.

If you don't need this variable, prefix it with an underscore like _guest to suppress this warning.
The unused variable is declared here:
   ┌─ main.roc:34:3
   34    guest = Guest()
      ^^^^^


Found 0 error(s) and 1 warning(s) for main.roc.
dbg: { age: 25, name: "String" }

Okay, lets uncomment it back and try to use the advice from the error you can ignore it with _ =.

  guest = Guest()
  _ = User.welcome(guest)

> got bommbarded with 5 errors, and same advice again

-- UNEXPECTED TOKEN IN EXPRESSION ----------------

The token _ is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.

   ┌─ main.roc:35:3
   35    _ = User.welcome(guest)
      ^

-- UNEXPECTED TOKEN IN EXPRESSION ----------------

The token = is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.

   ┌─ main.roc:35:5
   35    _ = User.welcome(guest)
        ^

-- UNRECOGNIZED SYNTAX ---------------------------

I don't recognize this syntax.

   ┌─ main.roc:35:3

35 │   _ = User.welcome(guest)
   │   ^

This might be a syntax error, an unsupported language feature, or a typo.
-- UNRECOGNIZED SYNTAX ---------------------------

I don't recognize this syntax.

   ┌─ main.roc:35:5
   35    _ = User.welcome(guest)
        ^

This might be a syntax error, an unsupported language feature, or a typo.
-- TYPE MISMATCH ---------------------------------

This expression produces a value, but it's not being used:
   ┌─ main.roc:35:7

35 │   _ = User.welcome(guest)
   │       ^^^^^^^^^^^^^^^^^^^

It has the type:

    Str

Since this expression is used as a statement, it must evaluate to {}.
If you don't need the value, you can ignore it with _ =.

Found 5 error(s) and 0 warning(s) for main.roc.

Maybe this text of the error is from the old roc, and not valid in new one? thats my guess.
Also why its so inconsistent, that unused result of a function is an error, and unused type constructor is a warrrning?

view this post on Zulip Jonathan (Apr 09 2026 at 22:43):

Not to say whether or not that's clear from the error messages given, but I see it as something like:

I didn't realise you couldn't assign to _ currently :thinking: It works for me if I write _myvarname = User.welcome(guest), and _ also works in matches.

view this post on Zulip Anton (Apr 10 2026 at 09:23):

I'm pretty sure we intended to support _ = something, I will implement it right now.

view this post on Zulip Anton (Apr 10 2026 at 10:17):

since I have background of langs with tagged unions in which fields can be attached directly to .. tag

I think the current approach falls in line with our desire to have a small number of simple language primitives that can easily be combined, but I will keep an ear out for more feedback on this.

view this post on Zulip Anton (Apr 10 2026 at 10:44):

Anton said:

I'm pretty sure we intended to support _ = something, I will implement it right now.

Done in PR#9332


Last updated: Apr 10 2026 at 12:38 UTC