Zulip Chat Archive
Stream: general
Topic: Get rid of `Except` when `.error` is impossible
nrs (Nov 25 2024 at 14:22):
The following is a Pratt parser. I'm trying to figure out whether it is possible to refactor the code to get rid of the extract function whose sole purpose is extracting the value from an EStateM.Result. As you can notice from the pattern match at nexttokmatch, we rule out the possibility that it errors in the places where we call extract nexttokmatch. Is there an alternative way of writing this code that makes this explicit, in some way maybe that requires supplying a proof? i.e., instead of a function like extract which leaves implicit the fact that its branch with default cannot be reached, I'd be looking for a function Except A B -> B that would make it clear an error is impossible (general style/writing suggestions are also welcome!)
set_option allowUnsafeReducibility true
mutual
partial def step (minBindingPower : Nat) : Lexer Sexp := do
let nexttokmatch : Except String Sexp := match (<- .next) with
| .atom c => .ok $ .atom c
| tok => throw "nexttokmatch: bad token: {tok}"
ReaderT.run (stepAux minBindingPower) nexttokmatch
partial abbrev stepAux (minBindingPower : Nat) : ReaderT (Except String Sexp) Lexer Sexp := do
let extract : Except String Sexp -> Sexp := (match . with | .ok c => c | _ => default) -- how do I get rid of this? it is impossible to reach default
let nexttokmatch <- .read
match (<- .peek) with
| .eof => return extract nexttokmatch
| .atom c => throw s!"peektokmatch: bad token: {c}"
| .op c => match infixBindingPower c with
| .error str => fun _ => .error str
| .ok (leftBindingPower, rightBindingPower) =>
if leftBindingPower < minBindingPower then
return extract nexttokmatch
else
discard .next
let rhs <- step rightBindingPower
let nextRun := Sexp.cons c #[extract nexttokmatch, rhs]
stepAux minBindingPower (.ok nextRun)
end
Eric Wieser (Nov 25 2024 at 14:56):
I'm not sure I agree that it is unreachable
nrs (Nov 25 2024 at 15:12):
Eric Wieser said:
I'm not sure I agree that it is unreachable
Either extract nexttokmatch is encountered from a call tree of shape step -> stepAux or it is encountered from a call tree of shape step -> stepAux -> ... -> stepAux. In the first case, the default branch is unreachable by virtue of the pattern match within step. In the second case, it is unreachable by virtue of .ok nextRun
Eric Wieser (Nov 25 2024 at 15:14):
I disagree with your analysis of the first case
Eric Wieser (Nov 25 2024 at 15:15):
The pattern match within step says "if (<- .next) is not an atom, then make .read return .error when it is first called."
nrs (Nov 25 2024 at 15:18):
Eric Wieser said:
The pattern match within
stepsays "if(<- .next)is not an atom, then make.readreturn.errorwhen it is first called."
ah you're right, I had misinterpreted throw as exiting the recursion
nrs (Nov 25 2024 at 15:19):
hm I have to reconsider the entire code in light of this. tyvm for the comments I would have not realized it otherwise!
nrs (Nov 25 2024 at 15:21):
extract then actually should either extract the value or propagate the error
Eric Wieser (Nov 25 2024 at 15:22):
Without knowing the definition of Lexer, it's hard to know if that is possible
nrs (Nov 25 2024 at 15:31):
Eric Wieser said:
Without knowing the definition of
Lexer, it's hard to know if that is possible
Here's the the entire context, if you're interested. We have abbrev Lexer (α : Type) := EStateM String (Array Token) α. The above code gets fixed by letting extract propage the error.
inductive Token
| atom : Char -> Token
| op : Char -> Token
| eof
deriving Repr
def Token.toString : Token -> String
| .atom c => s!"atom({c})"
| .op c => s!"op({c})"
| .eof => "eof"
instance : ToString Token where
toString := Token.toString
abbrev Lexer (α : Type) := EStateM String (Array Token) α
def mkLexer (ar : Array String) : Lexer Unit := do
set (ar |>.map (String.data
|> (List.toArray ∘ .))
|>.flatten
|>.reverse
|>.filter (not ∘ Char.isWhitespace)
|>.map (fun c => if Char.isAlphanum c then Token.atom c else Token.op c))
def Lexer.next : Lexer Token := do
let lastState <- get
set lastState.pop
if h : 0 < lastState.size then
return lastState[lastState.size - 1]
else
return .eof
def Lexer.peek : Lexer Token := do
let lastState <- get
if h : 0 < lastState.size then
return lastState[lastState.size - 1]
else
return .eof
inductive Sexp
| atom : Char -> Sexp
| cons : Char -> Array Sexp -> Sexp
deriving Inhabited, Repr
def Sexp.toString : Sexp -> String
| .atom c => s!" {c} "
| .cons c ar => s!"({c}" ++ Array.foldr (. ++ .) "" (Array.map Sexp.toString ar) ++ ")"
instance : ToString Sexp where
toString := Sexp.toString
def Sexp.append : Sexp -> Sexp -> Sexp
| .atom c, .atom cc => .cons c #[.atom cc]
| .atom c, .cons cc ar => .cons c (#[.atom cc] ++ ar)
| .cons c ar, .atom cc => .cons c (ar ++ #[.atom cc])
| .cons c ar, .cons cc arr => .cons c (ar ++ #[.atom cc] ++ arr)
instance : HAppend Sexp Sexp Sexp where
hAppend := Sexp.append
def infixBindingPower (c : Char) : Except String (Nat × Nat) :=
if c == '+' || c == '-' then
.ok (1, 2)
else if c == '*' || c == '/' then
.ok (3, 4)
else
throw s!"infixBindingPower: unknown operator: {c}"
set_option allowUnsafeReducibility true
mutual
partial def step (minBindingPower : Nat) : Lexer Sexp := do
let nexttokmatch : Except String Sexp := match (<- .next) with
| Token.atom c => .ok $ Sexp.atom c
| tok => throw "nexttokmatch: bad token: {tok}"
ReaderT.run (stepAux minBindingPower) nexttokmatch
partial abbrev stepAux (minBindingPower : Nat) : ReaderT (Except String Sexp) Lexer Sexp := do
let extract : Except String Sexp -> Sexp := (match . with | .ok c => c | _ => default)
let nexttokmatch <- .read
match (<- .peek) with
| Token.eof => return extract nexttokmatch
| .atom c => throw s!"peektokmatch: bad token: {c}"
| .op c => match infixBindingPower c with
| .error str => fun _ => .error str
| .ok (leftBindingPower, rightBindingPower) =>
if leftBindingPower < minBindingPower then
return extract nexttokmatch
else
discard .next
let rhs <- step rightBindingPower
let nextRun := Sexp.cons c #[extract nexttokmatch, rhs]
stepAux minBindingPower (.ok nextRun)
end
def tempmain : IO Unit := do
let lex : Lexer Sexp := do mkLexer #["a+b*c*d+e"]; step 0
match lex #[] with
| .error ermsg state => .error ermsg
| .ok val state => .println val
#eval! tempmain
Last updated: May 02 2025 at 03:31 UTC