Zulip Chat Archive
Stream: lean4
Topic: unknown identifier in `let var := if let`
Utensil Song (Oct 12 2023 at 06:35):
I managed to make a #mwe for something I encountered :
import Lean
open Lean System
def getOption (str : String) : Option FilePath := str
def getIOString (file : FilePath) : IO String := do
return file.toString
def works : IO String := do
if let some opt := getOption "str" then
let ret := (<- getIOString opt)
return ret
else
return "no option"
def also_works : IO String := do
let var <- if let some opt := getOption "str" then
let ret := (<- getIOString opt)
pure ret
else
pure "no option"
return var
def oops : IO String := do
let var := if let some opt := getOption "str" then
let ret := (<- getIOString opt) -- unknown identifier 'opt'
ret
else
"no option"
return var
#eval works -- "str"
#eval also_works -- "str"
#eval oops -- cannot evaluate code because 'oops' uses 'sorry' and/or contains errors
What's happening here?
Marcus Rossel (Oct 12 2023 at 06:44):
I think the unknown identifier
message is just unfortunate and the problem is actually related to the use of ←
in do
-notation. If you try this, it works:
def oops : IO String := do
let var ← if let some opt := getOption "str" then getIOString opt else return "no option"
return var
Utensil Song (Oct 12 2023 at 06:50):
Yes, that's the also_works case . I wonder why this message would show up, usually if I use :=
incorrectly I would get an error tell me to use the arrow.
Utensil Song (Oct 12 2023 at 06:52):
Seems some info is unavailable to Lean during elaboration
Marcus Rossel (Oct 12 2023 at 10:34):
Oh sorry, I totally ignored the other definitions :see_no_evil:
Eric Wieser (Oct 12 2023 at 11:54):
The fact this fails looks like expected behavior to me; after the :=
you are no longer in a monadic context, so the (<- getIOString opt)
has to be lifted all the way before it
Eric Wieser (Oct 12 2023 at 11:56):
I'm a bit surprised this doesn't work
def also_works : IO String := do
let var := (<- if let some opt := getOption "str" then
let ret := (<- getIOString opt)
pure ret
else
pure "no option")
I thought that let x <- f
and let x := (<- f)
meant the same thing
Mario Carneiro (Oct 12 2023 at 13:07):
it does work
Mario Carneiro (Oct 12 2023 at 13:08):
but the entire do block has no return value, so it complains about a Unit
/ String
mismatch on the last line
Mario Carneiro (Oct 12 2023 at 13:08):
putting _
after the do block makes the error go away
Mario Carneiro (Oct 12 2023 at 13:10):
Oh! The version with let x <- f
has no errors, which is definitely a bug (it apparently just ignores the x
binding and returns f
to the do block)
Utensil Song (Oct 12 2023 at 13:15):
Eric Wieser said:
The fact this fails looks like expected behavior to me; after the
:=
you are no longer in a monadic context, so the(<- getIOString opt)
has to be lifted all the way before it
Yes, this fails is expected, actually I was fiddling deep in levels of nesting even forgot the :=
that was already there so I didn't realize why until I minimize it. But the real wierd thing is the error unkown identifier, which creeps me out as it's another level of horror.
Usually, when I misuse :=
, <-
, := (<- ...)
, I get an informative error.
- expect
<-
, misuse:=
The error propagates down to
type mismatch, result value has type
String : Type
but is expected to have type
IO String : Type
which reminds me I'm dealing with Monad
- expect
:=
, misuse `<-'
type mismatch
"str"
has type
String : Type
but is expected to have type
IO ?m.1506 : Type
which also reminds me.
- even better, in
if let
, expect:=
, misuse `<-', gives me:
unexpected token '<-'; expected ':=' or '←'
- in
if let
, expect:= (<- ... )
, misuse:=
gives metype mismatch
, misuse<-
gives meunexpected token '<-'; expected ':=' or '←'
which corner me to:= (<- ...)
.
Utensil Song (Oct 12 2023 at 13:17):
I'll make a longer #mwe to produce all these in a sec.
Mario Carneiro (Oct 12 2023 at 13:44):
Utensil Song (Oct 12 2023 at 14:04):
EDIT: the following is improved and published to a LeanInk annotated output : https://utensil.github.io/lean-playground/Zulip/Arrow.html
import Lean
open Lean System
def getOpt (str : String) : Option String := str
def getIO (str : String) : IO String := pure str
def getOptIO (str : String) : IO (Option String) := pure (some str)
-- if let return
def works₁ : IO String := do
if let some opt := getOpt "str" then
let ret := (<- getIO opt)
return ret
else
return "none"
#eval works₁ -- works "str"
-- let var <- if let
def works₂ : IO String := do
let var <- if let some opt := getOpt "str" then
let ret := (<- getIO opt)
pure ret
else
pure "none"
return var
#eval works₂ -- works: "str"
-- let var := if let
def oops₀ : IO String := do
let var := if let some opt := getOpt "str" then
let ret := (<- getIO opt) -- unknown identifier 'opt'
ret
else
"none"
return var
-- 1. expect `<-`, misuse `:=`
def oops₁₁ : IO String := do
let some ret := getOptIO "str"
ret -- unknown identifier 'ret'
def oops₁₂ : IO String := do
let some ret := getOptIO "str" | pure "none" -- type mismatch: `IO (Option String)` got `Option ?m.2842`
pure ret
def works₁₃ : IO String := do
let some ret <- getOptIO "str" | pure "none"
pure ret -- works
-- 2. expect `:=`, misuse `<-`
def oops₂ : IO String := do
let io <- getIO "str"
io -- type mismatch: expected `IO String`, got `String`
-- 3. in `if let`, expect `:=`, misuse `<-`
def oops₃ : IO String := do
if let some opt <- getOpt "str" then -- unexpected token '<-'; expected ':=' or '←'
let ret := (<- getIO opt)
return ret
else
return "none"
-- 4 in `if let`, expect := (<- ... )
-- 4.1 misuse `:=` gives me type mismatch
def oops₄₁ : IO String := do
if let some opt := getOptIO "str" then -- type mismatch: expected `IO (Option String)`, got `Option ?m.3586`
let ret <- getIO opt
pure ret
else
pure "none"
-- 4.2 misues `<-`
def oops₄₂ : IO String := do
if let some opt <- getOptIO "str" then -- unexpected token '<-'; expected ':=' or '←'
let ret <- getIO opt
pure ret
else
pure "none"
def works₄₃ : IO String := do
if let some opt := (<- getOptIO "str") then
let ret <- getIO opt
pure ret
else
pure "none"
Utensil Song (Oct 12 2023 at 14:04):
OK, I've got all the variants I want to try, don't know if lean4#2676 fixes them all.
Utensil Song (Oct 12 2023 at 14:06):
The key ones are
oops₀
:let var := if let
- gives
unknown identifier
oops₁₁
- expect
<-
, misuse:=
- gives
unknown identifier
- expect
oops₄
- in
if let
, forces(<- ... )
- in
Eric Wieser (Oct 12 2023 at 14:07):
@Mario Carneiro, forgetting the final return was a typo. This still doesn't work:
import Lean
open Lean System
def getOption (str : String) : Option FilePath := str
def getIOString (file : FilePath) : IO String := do
return file.toString
def also_works : IO String := do
let var := (<- if let some opt := getOption "str" then
let ret := (<- getIOString opt)
pure ret
else
pure "no option")
pure var
Eric Wieser (Oct 12 2023 at 14:09):
(but works fine if I replace := (<- ...)
with <- ...
)
Utensil Song (Oct 12 2023 at 14:09):
Idk how to try the Lean in PR...
Mario Carneiro (Oct 12 2023 at 14:10):
this is I guess semi-expected
Mario Carneiro (Oct 12 2023 at 14:12):
in the variation := (<- ...)
the if let
expression is treated as a term, because (<- ...)
takes a term. Thus the inner <- getIOString
needs to be hoisted out of it (and because of another bug lean doesn't give a nice error message saying it can't lift <-
over a binder like it normally does) and you get an unknown variable error
Mario Carneiro (Oct 12 2023 at 14:14):
In the variation <- ...
the if let
is a doElem
, because the let x <- ...
doElem takes a doElem on the RHS (and it specially forbids things like let x <- let y <- 1
which are otherwise syntactically correct). I believe the reason this is done is so that control flow like if
and match
are still considered to be part of the same do block
Utensil Song (Oct 12 2023 at 14:21):
Utensil Song said:
The key ones are
oops₀
:
let var := if let
- gives
unknown identifier
oops₁₁
- expect
<-
, misuse:=
- gives
unknown identifier
oops₄
- in
if let
, forces(<- ... )
I can't relate the tests added in PR to these oops. The 2nd test gives me unknown identifier
, the 1st test gives me:
Mario Carneiro (Oct 12 2023 at 14:25):
the second test is a separate bug, I thought it was a duplicate but it's not
Mario Carneiro (Oct 12 2023 at 14:25):
the first test gives the error you are showing without the fix, but the fix should make it pass
Eric Wieser (Oct 12 2023 at 14:26):
Ah, so it sounds like the transformation is actually from let x <- f g
to let x := (<- do f g)
?
Mario Carneiro (Oct 12 2023 at 14:27):
...does that work? I would expect it to have a separate scope for mutation but now I'm not sure
Mario Carneiro (Oct 12 2023 at 14:29):
no luck:
#eval Id.run do
let mut x := 1
let y := (← do
x := x + 1 -- `x` cannot be mutated
pure ())
pure x
Mario Carneiro (Oct 12 2023 at 14:30):
this is because the do
term starts a new scope for mutation, return
etc while the do
doElem is just a grouping construct (i.e. it's the same issue)
Scott Morrison (Oct 12 2023 at 22:42):
Utensil Song said:
Idk how to try the Lean in PR...
@Utensil Song, once the PR compiles, you will be able to use it by setting your lean-toolchain
to leanprover/lean4-pr-releases:pr-release-2676
.
Scott Morrison (Oct 12 2023 at 22:43):
Alternatively you can compile the PR locally and use elan override set
following the instructions at https://lean-lang.org/lean4/doc/dev/index.html
Utensil Song (Oct 13 2023 at 01:30):
info: downloading component 'lean'
Error(Download(HttpStatus(404)), State { next_error: None, backtrace: InternalBacktrace { backtrace: None } })
error: could not download nonexistent lean version `leanprover/lean4-pr-releases:pr-release-2676`
info: caused by: could not download file from 'https://github.com/leanprover/lean4-pr-releases/releases/expanded_assets/pr-release-2676' to '/Users/utensil/.elan/tmp/f52sgiqoibbyouat_file'
info: caused by: http request returned an unsuccessful status code: 404
Maybe this method won't work for Mac M1? It seems most of the CI has passed. But maybe can't try it on Linux yet as well since one of the Linux CIs gives
[ ] Building Lean/Elab/Binders.lean
Lean/PrettyPrinter/Delaborator/Builtins.lean:672:4: error: application type mismatch
pure PUnit.unit
argument
PUnit.unit
has type
PUnit : Sort ?u.538015
but is expected to have type
Syntax × Name : Type
EDIT: tested on Codespaces (Ubuntu), not working either. (EDIT: I realized that I should check toolchain-available
label first, I thought the release CI has passed).
I'll try to build Lean now (following nix setup, ~45min on Codespaces).
Utensil Song (Oct 13 2023 at 04:05):
Tests from the PR pass for me, but oops₀
, oops₁₁
and oops₄
are not fixed per my test (same errors), and I've confirmed the Lean version by #eval Lean.githash
to be "4bb9a11e0c13a82682af6238e237f05e95a989ac" immediately before oops.
Don't know if I have missed something.
Last updated: Dec 20 2023 at 11:08 UTC