Hi! This one has me really confused. I have a compiling piece of code with a when … is
:
loop = \oldState ->
Stdout.line! "Checking git status…"
gitStatusOutput =
gitStatus!
|> outputToStr
newState =
getTimeSinceLastCommit!
|> (\timeSinceLastCommit -> stateFromGitStatus gitStatusOutput timeSinceLastCommit)
when (oldState, newState) is
(DirtySince _, Clean) ->
Stdout.line! "You are back into a clean state."
sleepOk newState
(DirtySince before, DirtySince now) if now - before > thresholdInSeconds ->
minutes = Num.toFrac thresholdInSeconds / 60 |> Num.round
Stdout.line! "You have been in a dirty state since more than $(if minutes > 1 then "minutes" else "minute")."
sleepOk newState
(DirtySince before, DirtySince now) ->
Stdout.line! "delta is supposed to be inferior or equal to threshold"
Stdout.line! (Inspect.toStr before)
Stdout.line! (Inspect.toStr now)
sleepOk newState
(Clean, DirtySince _) ->
# Just started changing files, all good
Stdout.line! ""
sleepOk newState
(Clean, Clean) ->
# Still clean, all good
Stdout.line! ""
sleepOk newState
_ ->
Stdout.line! (stateToStr newState)
sleepOk newState
But the catch-all branch at the end is useless because redundant: Clean
and DirtySince U128
are the only two possible states here. Yet when I try to remove it, I get this error:
This 2nd argument to loop has an unexpected type:
32│ Task.loop Initial loop
^^^^
This loop value is a:
[
Clean,
DirtySince (Int Unsigned128),
] -> Task [Step State, …] […]
But loop needs its 2nd argument to be:
[Initial]* -> Task [Step [Initial], …] […]
Tip: Seems like a tag typo. Maybe DirtySince should be Initial?
I really don’t get it, I don’t see the difference in the last couple branches in terms of type. Also I’m pretty sure of the exhaustiveness because if I remove another branch, I get an additional error about the unsafe pattern matching.
So the first tip I'd always recommend in these situations is to add type annotations where you have type issues like this
Second off, the type of Task.loop
is:
loop : state, (state -> Task [Step state, Done done] err) -> Task done err
loop = \state, step ->
looper = \current ->
(@Task next) = step current
when next {} is
Err e -> Err e
Ok (Done newResult) -> Ok newResult
Ok (Step newState) -> looper (newState)
@Task \{} -> looper state
So two things I can think of:
[Initial]*
, not [Clean, DirtySince I128]
Step newState
, you're just returning newState
I can't confirm either without more code to look at
In the error message you're given, it returns Task [Step State, ...] [...]
. What is State
?
I think this is a bug we have with when ... is
and tuples. I have seen it come up in the context of lists and tuples before
That said, hard to guarantee without seeing types
A thing I just noticed locally matching on a tuple is that if I accidentally add one redundant case to an already-exhaustive match, then rather than getting an error on the 'extra' case, I get the 'other possibilities include (_, _)' style error
Ok it probably has to do with the types at least, I had this type alias State, which is an alias to [Clean, DirtySince U128] but did not include Initial.
So I’ve added Initial to State, and now I need more branches, so I can’t quite yet get the exact same potential bug that I had before. Sorry about not being so reactive, I’ll follow up on this later, thanks all for you input!
I will say, it's not always great to hear "it'll get better so just wait a bit", but the purity inference PR is looking pretty good, so once we transition to that a lot of the Task-related typing woes can go away by using effectful functions instead of Tasks
That should happen sometime in the next couple weeks
Right! I fell asleep yesterday while watching the video, I need to watch the whole thing :sweat_smile:
Last updated: Jul 26 2025 at 12:14 UTC