Zulip Chat Archive
Stream: new members
Topic: Uncaught Exception with List.foldlM ?
iwooden (May 14 2024 at 00:56):
Hi all, learning Lean 4 with the "Functional Programming in Lean" book, been great fun so far!
I tried modifying the doug
utility introduced in chapter 7.1 to handle parsing multiple config arguments using ExceptT
. However, I get an uncaught exception
error when trying this that I don't understand - the minimally viable code I can find to reproduce this is, in Main.lean
:
abbrev ExceptStack := ExceptT String IO Nat
def argToConfig (n : Nat) : String → ExceptStack
| "--ok" => do
IO.println "valid arg processed"
.ok <| pure <| n+1
| _ => .error "invalid arg"
def buildConfig (xs : List String) : ExceptStack := do
xs.foldlM argToConfig 0
def main (args : List String) : IO Unit := do
match (← buildConfig args) with
| .ok n => IO.println s!"valid: {n}"
| .error err => IO.println err
Trying to run it gives me:
iwooden@mbp lean-except-bug % lake build
[0/2] Building Main
[1/2] Compiling Main
[2/2] Linking lean-except-bug
iwooden@mbp lean-except-bug % .lake/build/bin/lean-except-bug
valid: 0
iwooden@mbp lean-except-bug % .lake/build/bin/lean-except-bug --ok
valid arg processed
valid: 1
iwooden@mbp lean-except-bug % .lake/build/bin/lean-except-bug --notok
uncaught exception: invalid arg
The idea here is that I have some default object (in chapter 7.1 this would be the Config
), use something like List.foldlM
to make changes in that object for valid arguments, with "short-circuiting" for invalid arguments. Note that if I use Except String Nat
here instead of ExceptT String IO Nat
it works, but I'd like the IO
context available so I can do e.g. filesystem checks as part of the validation.
I have two questions:
- Why does this typecheck/compile? I would've hoped to avoid things like uncaught exceptions.
- What should I use instead of
List.foldlM
here? I can, of course, manually write a recursive function to traverse the list in this context, but I was hoping there's something in the standard library that would work.
Again, thanks so much for putting work into Lean and the great accompanying books! I'm having a great time, just this one issue has me scratching my head. Thanks!
iwooden (May 14 2024 at 05:55):
Ahhh, got it. After lots of trial and error, that .error
inside of argToConfig
is (for some reason) an EStateM.Result.error
instead of an Except.error
. Explicitly using the latter solves this issue and no longer results in an uncaught exception
.
Fairly confusing bug for a newcomer, but I guess being more explicit is one of the first things I should have tried.
iwooden (May 14 2024 at 06:10):
(although looking at it a bit more, EStateM
should work as well here? Looks like it's supposed to be equivalent to ExceptT ε (StateM σ)
, not sure why you'd get an uncaught exception from it)
Mario Carneiro (May 15 2024 at 20:57):
The issue is that .error
is not resolving the way you might hope/expect: it is EStateM.error
, which is the constructor for the exception state of the IO
monad, rather than the additional exception path you have added via ExceptT
. The String
you passed to it is coerced to a IO.Exception
and so since you didn't use the tryCatch
implementation of the IO
monad it propagated a userError "invalid arg"
out of main
and the runtime caught it.
What you should do instead is use throw
, which is the generic function provided by the MonadExcept
instance for ExceptT
(or even more precisely, throwThe
which allows you to specify which type you expect if the monad simultaneously supports throwing more than one type, in your case String
and IO.Exception
).
Last updated: May 02 2025 at 03:31 UTC