This should work right?
list : Task (List Str) *
list =
Effect.args
|> Effect.map Ok
|> InternalTask.fromEffect
foo =
Task.await list \argList ->
Task.err (Exit 0 "")
Error message:
── TYPE MISMATCH in Arg.roc ────────────────────────────────────────────────────
This 2nd argument to await has an unexpected type:
15│> Task.await list \argList ->
16│> Task.err (Exit 0 "")
The argument is an anonymous function of type:
List Str -> Task * [Exit (Num *) Str]
But await needs its 2nd argument to be:
List Str -> Task * *
I don't understand how this function can work but the code I posted does not.
No, I don't think it should work based on our current type system
......
But man, I hate *
a lot of the time.
I mean I love the clarity of it, but I hate how untuitive it can make bugs
Task.await: Task a b, (a -> Task c b) -> Task c b
Task a b ~ Task (List Str) *
a = List Str
b = * (note, this is the not in an output position, it is closed and can not merge with anything)
a -> Task c b ~ \argList -> Task.err (Exit 0 "")
c = *
b = [Exit (Num *) Str]
# we have just declared `b` as two different types...bug
I honestly think that *
should be restricted to only being allowed to be used in function signatures.
Then list
would be:
list : Task (List Str) []
.
The error becomes more clear. And you would probably change the type to:
list : Task (List Str) _
Or more accurately:
list : Task (List Str) []*
I don't understand how this function can work but the code I posted does not.
I think it works due to a compiler bug. I bet that we are interpreting []
as an open tag instead of a closed tag.
Arg.list
should be list: Task (List Str) []*
Thanks for the explanation @Brendan Hansknecht :heart:
With []*
I still can't get it to work, I've further simplified my code:
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
import pf.Stdout
import pf.Task exposing [Task]
okStr : Task Str []*
okStr =
Task.ok "aa"
foo =
Task.await okStr \str ->
Task.err (MyErr str)
main =
Stdout.line! "Hello, World!"
This is the error message:
TYPE MISMATCH
This 2nd argument to `await` has an unexpected type:
11│> Task.await okStr \str ->
12│> Task.err (MyErr str)
The argument is an anonymous function of type:
Str -> Task * [MyErr Str]
But `await` needs its 2nd argument to be:
Str -> Task * []*
Tip: Looks like a closed tag union does not have the `MyErr`
tag.
Tip: Closed tag unions can't grow, because that might change
the size in memory. Can you use an open tag union?
From the error message it seems like the compiler just erroneously detected a closed tag union?
I can use okStr : Task Str []_
, but type inference makes this okStr : Task Str [MyErr Str]
. This would require everyone who uses okStr
to handle the MyErr Str
error which can not actually happen.
Anton said:
I can use
okStr : Task Str []_
, but type inference makes thisokStr : Task Str [MyErr Str]
. This would require everyone who usesokStr
to handle theMyErr Str
error which can not actually happen.
If you write the same thing using Results, which are just an alias instead of an opaque type, can you still not use []*?
I've been running into this issue with Tasks for basic-cli, the current solution hack is to do
runTask : Task ok {}
runTask |> Task.result! |> Result.withDefault foo
It feels like opaque types prevent full type constraining from happening, in that Task doesn't actually look like a single tag Union to the compiler, though I'm not sure on this.
is this related to let polymorphism? What happens if you do okStr : {} -> Task Str []*
instead?
Richard Feldman said:
is this related to let polymorphism? What happens if you do
okStr : {} -> Task Str []*
instead?
If I change the Arg.list
signature to list : U64 -> Task (List Str) []*
I still get an error:
── TYPE MISMATCH in ../basic-cli/examples/../platform/Arg.roc ──────────────────
Something is off with the body of the parse definition:
83│ parse : CliParser state -> Task state [Exit I32 Str, StdoutErr Stdout.Err]
84│ parse = \parser ->
85│ # Stdout.line! "Parsing args..."
86│ arguments = list! 0
^^^^^^^
This await call produces:
Task state []*
But the type annotation on parse says it should be:
Task state [
Exit I32 Str,
StdoutErr Stdout.Err,
]
Tip: Looks like a closed tag union does not have the StdoutErr and
Exit tags.
Tip: Closed tag unions can't grow, because that might change the size
in memory. Can you use an open tag union?
If you write the same thing using Results, which are just an alias instead of an opaque type, can you still not use []*?
Yeah, I get the same thing using Result
What happens if you do
okStr : {} -> Task Str []*
instead?
That works!
Sam Mohr said:
Richard Feldman said:
is this related to let polymorphism? What happens if you do
okStr : {} -> Task Str []*
instead?If I change the
Arg.list
signature tolist : U64 -> Task (List Str) []*
I still get an error:
this one surprises me - @Ayaz Hafiz any ideas why it would be saying those don't unify?
I'm not sure, it's probably worth expanding out the types under the Task
alias
Related issue I think:
The effect:
ffiLoad : Str -> Effect (Result U64 Str)
ffiClose : U64 -> Effect (Result {} *)
ffiCall : U64, Str -> Effect (Result {} *)
With call : Lib, Str -> Task {} *
. Which should work based on ffiClose
working and being close : Lib -> Task {} *
:
Something is off with the body of the call definition:
46│ call : Lib, Str -> Task {} *
47│ call = \@Lib lib, fnName ->
48│> Effect.ffiCall lib fnName
49│> |> InternalTask.fromEffect
This InternalTask.fromEffect call produces:
InternalTask.Task {} []
But the type annotation on call says it should be:
Task.Task {} *
Changing to Task {} []
cause that seems to be what the error is telling me to do.
Something is off with the body of the call definition:
46│ call : Lib, Str -> Task {} []
47│ call = \@Lib lib, fnName ->
48│> Effect.ffiCall lib fnName
49│> |> InternalTask.fromEffect
This InternalTask.fromEffect call produces:
InternalTask.Task {} err
But the type annotation on call says it should be:
Task.Task {} []
Oh, I think I just realized something!
So if you dump the ir of the examples/tcp-client.roc
, the call to roc_fx_tcpClose
is incorrect. We can not have open tags in effects. They mean that the output has a dynamic layout that the platform is unaware of.
This is the effect: tcpClose : U64 -> Effect (Result {} *)
This is what rust expects: (u64) -> RocResult<(), ()>
This is the signiture that llvm is generating: declare void @roc_fx_tcpClose(ptr sret({ [0 x i64], [6 x i64], i8, [7 x i8] }), i64)
So the first arg is the result type and the second arg is the actual function input.
That means, it expects a result type of { [0 x i64], [6 x i64], i8, [7 x i8] }
i8, [7 x i8]
at the end is a tag for Ok/Err.
[0 x i64]
contains no data, so we don't care about it.
[6 x i64]
is 48 bytes of unaccounted for data.
What does that data represent? I'm pretty sure it's the union of all of the error types that build up in the task chain. In this case, the largest potential payload is TcpPerformErr (TcpReadErr (Unrecognized Str))
which is a Str (3 x i64) wrapped in a tag (i8, [7 x i8] aka 1xi64), wrapped in a tag (1xi64), wrapped in a tag (1xi64). For a total of 6 x i64.
This means that roc_fx_tcpClose
is returning a different type from rust than llvm is interpreting it as. There definitely needs to be a mapping generated here.
As a note, at some point we might want an optimization to collapse those tags together or avoid padding in some way.
whoa, great find!
Nice, so we should replace the wildcards in our effects/platformTasks with the unit type I guess.
So have a fixed closed tag provided to the host (e.g. effect/platform task), and to the app we can still have an open tag.
Yeah, this is probably something that needs to be codified as a restriction in the compiler
oh I think we definitely want to support it
I think we need to fix the code gen :big_smile:
How would we support it? I think that we might need to build a restricted type in rust and then have roc explicitly map the error/expand the type.
Like I dont think rust can construct the error tag in general here. Cause it only knows the local errors, not the full set of errors that the app unified with.
oh sorry, is this in the platform?
yeah the platform shouldn’t be able to tell the host it has an unbound variable
The platform specifies an effect like:
Effect (Result {} *)
Roc is generate code that is expecting it to return a Result {} UnifiedErrorTag
ahh yeah, I see what you mean avoiding restricting it now - yeah, that should be a compile-time error
I'm making the suggested changes to tcpClose
. Given that we always return ok here, should we not be returning {}
instead, just like roc_fx_ttyModeCanonical
?
#[no_mangle]
pub extern "C" fn roc_fx_tcpClose(stream_id: u64) -> RocResult<(), ()> {
TCP_STREAMS.with(|tcp_streams_local| {
tcp_streams_local.borrow_mut().remove(&stream_id);
});
RocResult::ok(())
}
Sidenote: For those messing with this general error unification problem, you may hit a case where you can not remove the *
in []*
even though it is reported as unnecessary. I made #6873 for that issue.
Last updated: Jul 05 2025 at 12:14 UTC