Unlocking the Future of AI Agents with Large Language Models
Transform your State \/ Err
1. @gerferra
Transform your
State / Err
Transform your
State / Err
An example of functional parsing with
state and error handling
An example of functional parsing with
state and error handling
@gerferra
3. Functional parsing
Write a parser using “just functions” ...
… within some programming language
… in the form of some embedded DSL
4. Functional parsing
All of this has a proper name:
Parser combinators
A collection of basic parsing functions that recognise a
piece of input
A collection of combinators that build new parsers out of
existing ones
http://www.fing.edu.uy/inco/cursos/pfa/uploads/Main/parsercombinators
5. Parser combinators
Scala has many parsing combinator libraries
Two common ones:
scala-parser-combinators
parboiled2
6. Parser combinators
Scala has many parsing combinator libraries
Two common ones:
scala-parser-combinators
parboiled2
The one we will be using
7. Parser combinators
Scala has many parsing combinator libraries
Two common ones:
scala-parser-combinators
parboiled2 Not really parser combinators ...
but very fast!
8. Parser combinators
Scala has many parsing combinator libraries
Two common ones:
scala-parser-combinators
parboiled2
New kid on the block: lihaoyi/fastparse
9. scala-parser-combinators
Part of the Scala standard library but distributed
separately
libraryDependencies +=
"org.scala-lang.modules" %%
"scala-parser-combinators" % "1.0.4"
10. scala-parser-combinators
Slow but good enough in this case
The first (?) parser combinator library for Scala
Many (but not all) of the other parser
combinator libraries use similar names/symbols
for the combinators
16. scala-parser-combinators
trait Parsers {
type Input
sealed trait ParseResult[+T]
trait Parser[+T] extends
(Input => ParseResult[T])
}
Success[+T] | NoSuccess
...plus the input not consumed
17. scala-parser-combinators
trait Parsers {
type Input
sealed trait ParseResult[+T]
trait Parser[+T] extends
(Input => ParseResult[T])
}
Success[+T] | NoSuccess
...plus the input not consumed
~ Input => (T, Input)
18. scala-parser-combinators
trait Parsers {
type Input
sealed trait ParseResult[+T]
trait Parser[+T] extends
(Input => ParseResult[T])
} A Parser is a function taking some input and
returning either some value of type T, or a failure
Success[+T] | NoSuccess
...plus the input not consumed
19. scala-parser-combinators
trait Parsers {
type Input
sealed trait ParseResult[+T]
trait Parser[+T] extends
(Input => ParseResult[T])
} A Parser is a function taking some input and
returning either some value of type T, or a failure
Success[+T] | NoSuccess
...plus the input not consumed
21. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
22. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
Many things
going on
23. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
We are evaluating
the result directly,
avoiding construct
an AST
24. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
Precedence rules
are encoded in the
grammar
25. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
This is evaluating
from right to left.
If you need to
control
associativity, the
rep combinator is
your friend.
26. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
Regular expressions
are converted to
Parsers implicitly
27. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
^^ is an alias of
the map function
28. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
Allows to convert
the Parser[String]
to Parser[Int]^^ is an alias of
the map function
29. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
Allows to convert
the Parser[String]
to Parser[Int]
String => Int
^^ is an alias of
the map function
30. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
Parse two inputs in
sequence discarding the
input of the left or the
right respectively
31. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
Pattern matching
anonymous function
32. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
Pattern matching
anonymous function
Allows to
deconstruct the
result of this parser
33. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
Pattern matching
anonymous function
Allows to
deconstruct the
result of this parser
Int Int
String
“+”
discarded
Int
34. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
We want a list of
all the expressions
we encounter
separated by “;”
35. Returning to our language
object Parser1 extends RegexParsers {
def pL1: Parser[List[Int]] = rep(pExpr <~ ";")
def pExpr = pAdd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ { case left ~ _ ~ right => left + right } |
pProd
def pProd: Parser[Int] =
pAtom ~ "*" ~ pProd ^^ { case left ~ _ ~ right => left * right } |
pAtom
def pAtom: Parser[Int] = """d+""".r ^^ (_.toInt) | "(" ~> pExpr <~ ")"
}
All good?
39. What if we want to add variables?
We need to handle two more cases:
Assignation of variables
References to variables
40. What if we want to add variables?
We need to handle two more cases:
Assignation of variables
References to variables
def pRef = pName ^^ {
x => get(symtab, x)
}
def pVar =
"var" ~ pName ~ "=" ~ pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab, name, expr)
}
41. We need to handle two more cases:
Assignation of variables
References to variables
And we need to carry a symbol table ...
def pVar =
"var" ~ pName ~ "=" ~ pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab, name, expr)
}
What if we want to add variables?
?
def pRef = pName ^^ {
x => get(symtab, x)
}
42. Carrying a symbol table
On pRef we need to access the symbol table
but leave it unmodified
On pVar we need to update the symbol table
Otherwise we just need to pass it untouched
def pRef = pName ^^ (x => get(symtab, x))
def pVar =
"var" ~ pName ~ "=" ~ pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab, name, expr)
}
43. Enter scalaz.State[S,A]
scalaz has a type that represents state
transitions
State[S,A] wraps functions with type:
S => (S, A)
Current state
New state
Value computed
45. Enter scalaz.State[S,A]
We need to change the type of our parsers ...
Parser[A]
Parser[State[S,A]]
Parser[State[SymTab, A]]
Parser[S[A]]
P[A]
46. Enter scalaz.State[S,A]
We need to change the type of our parsers ...
We now return A, but
in the context of some
stateful computationParser[A]
Parser[State[S,A]]
Parser[State[SymTab, A]]
Parser[S[A]]
P[A]
47. Enter scalaz.State[S,A]
We need to change the type of our parsers ...
We now return A, but
in the context of some
stateful computation
Our state is the
symbol table
~Map[String, Int]
Parser[A]
Parser[State[S,A]]
Parser[State[SymTab, A]]
Parser[S[A]]
P[A]
51. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pAdd: P[Int] =
pProd ~ "+" ~ pAdd ^^ {
case leftS ~ _ ~ rightS =>
for {
left <- leftS
right <- rightS
} yield {
left + right
}
} | pProd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ {
case left ~ _ ~ right =>
left + right
} |
pProd
52. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pAdd: P[Int] =
pProd ~ "+" ~ pAdd ^^ {
case leftS ~ _ ~ rightS =>
for {
left <- leftS
right <- rightS
} yield {
left + right
}
} | pProd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ {
case left ~ _ ~ right =>
left + right
} |
pProd
P[Int] == Parser[S[Int]]
53. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pAdd: P[Int] =
pProd ~ "+" ~ pAdd ^^ {
case leftS ~ _ ~ rightS =>
for {
left <- leftS
right <- rightS
} yield {
left + right
}
} | pProd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ {
case left ~ _ ~ right =>
left + right
} |
pProd
P[Int] == Parser[S[Int]]
S[Int]
54. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pAdd: P[Int] =
pProd ~ "+" ~ pAdd ^^ {
case leftS ~ _ ~ rightS =>
for {
left <- leftS
right <- rightS
} yield {
left + right
}
} | pProd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ {
case left ~ _ ~ right =>
left + right
} |
pProd
P[Int] == Parser[S[Int]]
S[Int]
leftS and rightS are
S[Int] and can’t be
added directly
55. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pAdd: P[Int] =
pProd ~ "+" ~ pAdd ^^ {
case leftS ~ _ ~ rightS =>
for {
left <- leftS
right <- rightS
} yield {
left + right
}
} | pProd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ {
case left ~ _ ~ right =>
left + right
} |
pProd
P[Int] == Parser[S[Int]]
S[Int]
But State[S,A]
is a Monad[A]
State[SymTab,A]
56. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pAdd: P[Int] =
pProd ~ "+" ~ pAdd ^^ {
case leftS ~ _ ~ rightS =>
for {
left <- leftS
right <- rightS
} yield {
left + right
}
} | pProd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ {
case left ~ _ ~ right =>
left + right
} |
pProd
P[Int] == Parser[S[Int]]
S[Int]
State[SymTab,A]
So we can use a for-
comprehension to
work with the ints
57. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pAdd: P[Int] =
pProd ~ "+" ~ pAdd ^^ {
case leftS ~ _ ~ rightS =>
for {
left <- leftS
right <- rightS
} yield {
left + right
}
} | pProd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ {
case left ~ _ ~ right =>
left + right
} |
pProd
P[Int] == Parser[S[Int]]
S[Int]
State[SymTab,A]
When we treat
State[S,A] as a Monad[A],
we work with the values,
and the state transitions
happens in the background
58. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pAdd: P[Int] =
pProd ~ "+" ~ pAdd ^^ {
case leftS ~ _ ~ rightS =>
for {
left <- leftS
right <- rightS
} yield {
left + right
}
} | pProd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ {
case left ~ _ ~ right =>
left + right
} |
pProd
P[Int] == Parser[S[Int]]
S[Int]
State[SymTab,A]
Here we are chaining
three state transitions
s0 => (s1, left)
s1 => (s2, right)
s2 => (s2, left+right)
59. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pAdd: P[Int] =
pProd ~ "+" ~ pAdd ^^ {
case leftS ~ _ ~ rightS =>
for {
left <- leftS
right <- rightS
} yield {
left + right
}
} | pProd
def pAdd: Parser[Int] =
pProd ~ "+" ~ pAdd ^^ {
case left ~ _ ~ right =>
left + right
} |
pProd
P[Int] == Parser[S[Int]]
S[Int]
State[SymTab,A]
Which can be seen as one
big transition:
s0 => ... (s2, left+right)
The State[S,A] we return
60. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pRef: Parser[Int] =
pName ^^ {
x => get(symtab, x)
}
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
61. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pRef: Parser[Int] =
pName ^^ {
x => get(symtab, x)
}
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
Here we need to
access to the symbol
table—our state
62. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pRef: Parser[Int] =
pName ^^ {
x => get(symtab, x)
}
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
The trick of State.get is to
create a new State[S,S]
where the state is used as
the value
63. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pRef: Parser[Int] =
pName ^^ {
x => get(symtab, x)
}
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
The trick of State.get is to
create a new State[S,S]
where the state is used as
the value
Wraps a state
transition
{ s => (s, s) }
64. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pRef: Parser[Int] =
pName ^^ {
x => get(symtab, x)
}
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
Wraps a state
transition
{ s => (s, s) }
With such State[S,S]
created, now we can use
again a for-comprehension to
work with the symbol table
(the value)
65. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pRef: Parser[Int] =
pName ^^ {
x => get(symtab, x)
}
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
Wraps a state
transition
{ s => (s, s) }
We are chaining two state
transitions:
s => (s, s)
s => (s, s(name))
66. … and change the parsers themselves a bit
Enter scalaz.State[S,A]
def pRef: Parser[Int] =
pName ^^ {
x => get(symtab, x)
}
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
Wraps a state
transition
{ s => (s, s) }
Which can be seen as:
s => ... (s, s(name))
The State[SymTab, Int] == S[Int] we return
67. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
68. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
The last case
69. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
We need to
modify the state
70. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
We need to
modify the state
S[Int]
71. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
We need to
modify the state
S[SymTab]
S[Int]
72. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
To change the state
we use State.put
S[SymTab]
S[Int]
73. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
Given s, it creates a
State[S,Unit] representing
this state transition:
{ _ => (s, ()) }
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
S[SymTab]
S[Int]
74. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
This discards any previous
state in favor of the one
provided, and returns ()
{ _ => (s, ()) }
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
S[SymTab]
S[Int]
75. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
So, the value we get in the
for-comprehension is the
unit
S[Unit]
S[SymTab]
S[Int]
76. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
We have four state transitions:
s0 => (s1, expr)
s1 => (s1, s1)
_ => (s2=s1+(name->expr), ())
s2 => (s2, ())
S[Unit]
S[SymTab]
S[Int]
77. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
So we are returning:
s0 => … (s1+(name->expr), ())
State[SymTab, Unit] == S[Unit] S[Unit]
S[SymTab]
S[Int]
78. Enter scalaz.State[S,A]
… and change the parsers themselves a bit
def pVar: Parser[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ expr =>
put(symtab,name,expr)
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
Now we know the basics
of the State monad
79. Running the state
import Parser2._
parse(pExpr, "1") //> [1.2] parsed: scalaz.
package$State$$anon$3@7f560810
80. Running the state
import Parser2._
parse(pExpr, "1") //> [1.2] parsed: scalaz.
package$State$$anon$3@7f560810
Our parser now returns State[SymTab,A], not
directly a value
81. Running the state
import Parser2._
parse(pExpr, "1") //> [1.2] parsed: scalaz.
package$State$$anon$3@7f560810
To compute the final value, we need to provide
an initial state and then all the state transitions
can be run
82. Running the state
import Parser2._
parse(pExpr, "1").map(state => state.run(Map.empty))
//> [1.2] parsed: (Map(),1)
To compute the final value, we need to provide
an initial state and then all the state transitions
can be run
83. Running the state
import Parser2._
parse(pExpr, "1").map(state => state.run(Map.empty))
//> [1.2] parsed: (Map(),1)
To compute the final value, we need to provide
an initial state and then all the state transitions
can be run
map over ParseResult[S[Int]]
84. Running the state
import Parser2._
parse(pExpr, "1").map(state => state.run(Map.empty))
//> [1.2] parsed: (Map(),1)
To compute the final value, we need to provide
an initial state and then all the state transitions
can be run
map over ParseResult[S[Int]]
Initial state
85. Running the state
import Parser2._
parse(pExpr, "1").map(state => state.run(Map.empty))
//> [1.2] parsed: (Map(),1)
To compute the final value, we need to provide
an initial state and then all the state transitions
can be run
map over ParseResult[S[Int]]
Initial state
We obtain the final state and
the value computed
86. Running the state
import Parser2._
parse(pExpr, "1").map(_.run(Map.empty))
//> [1.2] parsed: (Map(),1)
parse(pVar, "var x = 1").map(_.run(Map.empty))
//> [1.10] parsed: (Map(x -> 1),())
parse(pExpr, "y * 3").map(_.run(Map("y" -> 2)))
//> [1.6] parsed: (Map(y -> 2),6)
87. Running the state
import Parser2._
parse(pExpr, "1").map(_.run(Map.empty))
//> [1.2] parsed: (Map(),1)
parse(pVar, "var x = 1").map(_.run(Map.empty))
//> [1.10] parsed: (Map(x -> 1),())
parse(pExpr, "y * 3").map(_.run(Map("y" -> 2)))
//> [1.6] parsed: (Map(y -> 2),6)
The final state reflects the
binding of 1 to x
88. Running the state
import Parser2._
parse(pExpr, "1").map(_.run(Map.empty))
//> [1.2] parsed: (Map(),1)
parse(pVar, "var x = 1").map(_.run(Map.empty))
//> [1.10] parsed: (Map(x -> 1),())
parse(pExpr, "y * 3").map(_.run(Map("y" -> 2)))
//> [1.6] parsed: (Map(y -> 2),6)
The final state reflects the
binding of 1 to x
We can provide bindings in the
initial state
89. Running the state
parse(pL2, """var x = 1;
|var y = x * 3;
|x + y;
|(x + 1) * 3 + 1;
|var z = 8;""".stripMargin).map(_.run(Map.empty))
//> [5.11] parsed: (Map(x -> 1, y -> 3, z -> 8),List(4, 7))
91. Now ...
What if we reference an undeclared
variable?
parse(pL2, "var x = y;").map(_.run(Map.empty))
92. Now ...
What if we reference an undeclared
variable?
parse(pL2, "var x = y;").map(_.run(Map.empty))
Exception in thread "main" java.util.NoSuchElementException: key not found: y
at scala.collection.MapLike$class.default(MapLike.scala:228)
at scala.collection.AbstractMap.default(Map.scala:59)
at scala.collection.MapLike$class.apply(MapLike.scala:141)
at scala.collection.AbstractMap.apply(Map.scala:59)
...
93. Handling errors
If our computation can fail, then our types are
wrong. We can’t just return A
94. Handling errors
If our computation can fail, then our types are
wrong. We can’t just return A
We need to handle
References to undeclared variables
Declarations of already declared variables
95. scalaz./
/[A,B] == A/B, called “or”, “xor”, “either”,
“disjoint union”,..., can be used to handle the
cases when a computation can fail
A / B
The type of the error.
By convention on the left
The type of the value we
are computing.
By convention on the right
97. Handling errors
We will have to change our types again:
type V[A] = Err / A
type S[A] = State[SymTab, V[A]]
sealed abstract class Err(override val toString: String)
case class NotDeclared(name: String) extends Err(s"`$name' not declared.")
case class AlreadyDeclared(name: String, value: Int)
extends Err(s"`$name' already declared with value `$value'.")
100. It gets ugly ...
def pVar: P[Unit] = "var" ~ pName ~ "=" ~ pExpr ^^ { case _ ~ name ~ _ ~ exprS =>
for {
exprV <- exprS
symtab <- State.get[SymTab]
newSymTab = for {
expr <- exprV
s <- symtab.get(name) match {
// if the name exists, creates a left with the error
case Some(v) => AlreadyDeclared(name, v).left
// if not exists, returns the symtab as a right
case None => symtab.right
}
} yield { s + (name -> expr) // only executes if everything is right }
res <- newSymTab match {
// if the newSymTab is a left, I need to put the error in the State
case -/(l) => State.state[SymTab, V[Unit]](l.left)
// if the newSymTab is a right, I need to use it as the new state, and put the () inside a /
case /-(r) => State.put(r).map(_.right)
}
} yield { res }
}
101. It gets ugly ...
def pVar: P[Unit] = "var" ~ pName ~ "=" ~ pExpr ^^ { case _ ~ name ~ _ ~ exprS =>
for {
exprV <- exprS
symtab <- State.get[SymTab]
newSymTab = for {
expr <- exprV
s <- symtab.get(name) match {
// if the name exists, creates a left with the error
case Some(v) => AlreadyDeclared(name, v).left
// if not exists, returns the symtab as a right
case None => symtab.right
}
} yield { s + (name -> expr) // only executes if everything is right }
res <- newSymTab match {
// if the newSymTab is a left, I need to put the error in the State
case -/(l) => State.state[SymTab, V[Unit]](l.left)
// if the newSymTab is a right, I need to use it as the new state, and put the () inside a /
case /-(r) => State.put(r).map(_.right)
}
} yield { res }
}
This is just one parser
102. It gets ugly ...
def pVar: P[Unit] = "var" ~ pName ~ "=" ~ pExpr ^^ { case _ ~ name ~ _ ~ exprS =>
for {
exprV <- exprS
symtab <- State.get[SymTab]
newSymTab = for {
expr <- exprV
s <- symtab.get(name) match {
// if the name exists, creates a left with the error
case Some(v) => AlreadyDeclared(name, v).left
// if not exists, returns the symtab as a right
case None => symtab.right
}
} yield { s + (name -> expr) // only executes if everything is right }
res <- newSymTab match {
// if the newSymTab is a left, I need to put the error in the State
case -/(l) => State.state[SymTab, V[Unit]](l.left)
// if the newSymTab is a right, I need to use it as the new state, and put the () inside a /
case /-(r) => State.put(r).map(_.right)
}
} yield { res }
}
exprS now contains a /[Err,Int]
103. It gets ugly ...
def pVar: P[Unit] = "var" ~ pName ~ "=" ~ pExpr ^^ { case _ ~ name ~ _ ~ exprS =>
for {
exprV <- exprS
symtab <- State.get[SymTab]
newSymTab = for {
expr <- exprV
s <- symtab.get(name) match {
// if the name exists, creates a left with the error
case Some(v) => AlreadyDeclared(name, v).left
// if not exists, returns the symtab as a right
case None => symtab.right
}
} yield { s + (name -> expr) // only executes if everything is right }
res <- newSymTab match {
// if the newSymTab is a left, I need to put the error in the State
case -/(l) => State.state[SymTab, V[Unit]](l.left)
// if the newSymTab is a right, I need to use it as the new state, and put the () inside a /
case /-(r) => State.put(r).map(_.right)
}
} yield { res }
}
I’m using an extra for-
comprehension to deal
with it
104. It gets ugly ...
def pVar: P[Unit] = "var" ~ pName ~ "=" ~ pExpr ^^ { case _ ~ name ~ _ ~ exprS =>
for {
exprV <- exprS
symtab <- State.get[SymTab]
newSymTab = for {
expr <- exprV
s <- symtab.get(name) match {
// if the name exists, creates a left with the error
case Some(v) => AlreadyDeclared(name, v).left
// if not exists, returns the symtab as a right
case None => symtab.right
}
} yield { s + (name -> expr) // only executes if everything is right }
res <- newSymTab match {
// if the newSymTab is a left, I need to put the error in the State
case -/(l) => State.state[SymTab, V[Unit]](l.left)
// if the newSymTab is a right, I need to use it as the new state, and put the () inside a /
case /-(r) => State.put(r).map(_.right)
}
} yield { res }
}
Some juggling to construct a left
value if the symbol table already
has the name
105. It gets ugly ...
def pVar: P[Unit] = "var" ~ pName ~ "=" ~ pExpr ^^ { case _ ~ name ~ _ ~ exprS =>
for {
exprV <- exprS
symtab <- State.get[SymTab]
newSymTab = for {
expr <- exprV
s <- symtab.get(name) match {
// if the name exists, creates a left with the error
case Some(v) => AlreadyDeclared(name, v).left
// if not exists, returns the symtab as a right
case None => symtab.right
}
} yield { s + (name -> expr) // only executes if everything is right }
res <- newSymTab match {
// if the newSymTab is a left, I need to put the error in the State
case -/(l) => State.state[SymTab, V[Unit]](l.left)
// if the newSymTab is a right, I need to use it as the new state, and put the () inside a /
case /-(r) => State.put(r).map(_.right)
}
} yield { res }
}
If either exprV or this contains a
left, all the for-comprehension will
be a left, ie, newSymTab will be a
left
106. It gets ugly ...
def pVar: P[Unit] = "var" ~ pName ~ "=" ~ pExpr ^^ { case _ ~ name ~ _ ~ exprS =>
for {
exprV <- exprS
symtab <- State.get[SymTab]
newSymTab = for {
expr <- exprV
s <- symtab.get(name) match {
// if the name exists, creates a left with the error
case Some(v) => AlreadyDeclared(name, v).left
// if not exists, returns the symtab as a right
case None => symtab.right
}
} yield { s + (name -> expr) // only executes if everything is right }
res <- newSymTab match {
// if the newSymTab is a left, I need to put the error in the State
case -/(l) => State.state[SymTab, V[Unit]](l.left)
// if the newSymTab is a right, I need to use it as the new state, and put the () inside a /
case /-(r) => State.put(r).map(_.right)
}
} yield { res }
}
Here I’m “folding” over the
/[Err, SymTab]. If is a left, I
need to put that inside the
State. If is a right, I need to
use the value as the new state
107. It gets ugly ...
def pVar: P[Unit] = "var" ~ pName ~ "=" ~ pExpr ^^ { case _ ~ name ~ _ ~ exprS =>
for {
exprV <- exprS
symtab <- State.get[SymTab]
newSymTab = for {
expr <- exprV
s <- symtab.get(name) match {
// if the name exists, creates a left with the error
case Some(v) => AlreadyDeclared(name, v).left
// if not exists, returns the symtab as a right
case None => symtab.right
}
} yield { s + (name -> expr) // only executes if everything is right }
res <- newSymTab match {
// if the newSymTab is a left, I need to put the error in the State
case -/(l) => State.state[SymTab, V[Unit]](l.left)
// if the newSymTab is a right, I need to use it as the new state, and put the () inside a /
case /-(r) => State.put(r).map(_.right)
}
} yield { res }
}
-/ is the extractor of left
values, /- is the extractor of
right values
108. It gets ugly ...
def pVar: P[Unit] = "var" ~ pName ~ "=" ~ pExpr ^^ { case _ ~ name ~ _ ~ exprS =>
for {
exprV <- exprS
symtab <- State.get[SymTab]
newSymTab = for {
expr <- exprV
s <- symtab.get(name) match {
// if the name exists, creates a left with the error
case Some(v) => AlreadyDeclared(name, v).left
// if not exists, returns the symtab as a right
case None => symtab.right
}
} yield { s + (name -> expr) // only executes if everything is right }
res <- newSymTab match {
// if the newSymTab is a left, I need to put the error in the State
case -/(l) => State.state[SymTab, V[Unit]](l.left)
// if the newSymTab is a right, I need to use it as the new state, and put the () inside a /
case /-(r) => State.put(r).map(_.right)
}
} yield { res }
}
State.state allows to put a
value inside a State[S,A].
For a given `a’, represents a
transition { s => (s, a) }
109. It gets ugly ...
def pVar: P[Unit] = "var" ~ pName ~ "=" ~ pExpr ^^ { case _ ~ name ~ _ ~ exprS =>
for {
exprV <- exprS
symtab <- State.get[SymTab]
newSymTab = for {
expr <- exprV
s <- symtab.get(name) match {
// if the name exists, creates a left with the error
case Some(v) => AlreadyDeclared(name, v).left
// if not exists, returns the symtab as a right
case None => symtab.right
}
} yield { s + (name -> expr) // only executes if everything is right }
res <- newSymTab match {
// if the newSymTab is a left, I need to put the error in the State
case -/(l) => State.state[SymTab, V[Unit]](l.left)
// if the newSymTab is a right, I need to use it as the new state, and put the () inside a /
case /-(r) => State.put(r).map(_.right)
}
} yield { res }
}
...
112. Monad transformers
The only thing I know about them, is that they
let you combine the effects of two monads, ...
113. Monad transformers
The only thing I know about them, is that they
let you combine the effects of two monads, ...
… scalaz has a monad transformer, StateT,
that let you combine the State monad with any
other monad, …
114. Monad transformers
The only thing I know about them, is that they
let you combine the effects of two monads, ...
… scalaz has a monad transformer, StateT,
that let you combine the State monad with any
other monad, …
… and this solves all our problems
115. Combining State and /
This are our final type aliases
type SymTab = Map[String, Int]
type V[A] = Err / A
type S[A] = StateT[V, SymTab, A]
type P[A] = Parser[S[A]]
116. Combining State and /
This are our final type aliases
type SymTab = Map[String, Int]
type V[A] = Err / A
type S[A] = StateT[V, SymTab, A]
type P[A] = Parser[S[A]]
This is the only
new one
117. Combining State and /
This are our final type aliases
type SymTab = Map[String, Int]
type V[A] = Err / A
type S[A] = StateT[V, SymTab, A]
type P[A] = Parser[S[A]]
StateT is
parameterized
on the monad
with which is
combined
118. Combining State and /
This are our final type aliases
type SymTab = Map[String, Int]
type V[A] = Err / A
type S[A] = StateT[V, SymTab, A]
type P[A] = Parser[S[A]]
Here is using
the monad of
/[A,B]
119. Combining State and /
And one final ingredient
val State = StateT.stateTMonadState[SymTab, V]
120. Combining State and /
And one final ingredient
val State = StateT.stateTMonadState[SymTab, V]
The companion object of StateT does not have
the functions get, put, state, etc.
121. Combining State and /
And one final ingredient
val State = StateT.stateTMonadState[SymTab, V]
Instead we have to instantiate one of this
“beasts”
122. Combining State and /
With that changes, our parser works as before
without any modification
123. Combining State and /
import Parser4._
parse(pExpr, "1").map(_.run(Map.empty))
//> [1.2] parsed: /-((Map(),1))
parse(pVar, "var x = 1").map(_.run(Map.empty))
//> [1.10] parsed: /-((Map(x -> 1),()))
parse(pExpr, "y * 3").map(_.run(Map("y" -> 2)))
//> [1.6] parsed: /-((Map(y -> 2),6))
124. Combining State and /
parse(pL4, """var x = 1;
|var y = x * 3;
|x + y;
|(x + 1) * 3 + 1;
|var z = 8;""".stripMargin).map(_.run(Map.empty))
//> [5.11] parsed: /-((Map(x -> 1, y -> 3, z -> 8),List(4, 7)))
125. Combining State and /
parse(pL4, """var x = 1;
|var y = x * 3;
|x + y;
|(x + 1) * 3 + 1;
|var z = 8;""".stripMargin).map(_.run(Map.empty))
//> [5.11] parsed: /-((Map(x -> 1, y -> 3, z -> 8),List(4, 7)))
The only difference is that now the results are inside a /, in
particular a /-, ie, a right value, which indicates that
everything is OK.
127. Handling errors
Everything else is working the same, even our
exceptions when an undeclared variable is
referenced
We must change pRef y pVar to handle the
error cases
128. Handling errors
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
def pRef: P[Int] =
pName ^^ {
name => for {
symtab <- State.get
vErrInt =
symtab.get(name)
.toRightDisjunction(
NotDeclared(name))
res <-
StateT[V, SymTab, Int] {
s => vErrInt.map(s -> _)
}
} yield { res }
}
129. Handling errors
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
def pRef: P[Int] =
pName ^^ {
name => for {
symtab <- State.get
vErrInt =
symtab.get(name)
.toRightDisjunction(
NotDeclared(name))
res <-
StateT[V, SymTab, Int] {
s => vErrInt.map(s -> _)
}
} yield { res }
}
We are converting an Option[Int]
into /[Err,Int]
130. Handling errors
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
def pRef: P[Int] =
pName ^^ {
name => for {
symtab <- State.get
vErrInt =
symtab.get(name)
.toRightDisjunction(
NotDeclared(name))
res <-
StateT[V, SymTab, Int] {
s => vErrInt.map(s -> _)
}
} yield { res }
}
We construct our StateT “by
hand” by passing the function
directly
131. Handling errors
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
def pRef: P[Int] =
pName ^^ {
name => for {
symtab <- State.get
vErrInt =
symtab.get(name)
.toRightDisjunction(
NotDeclared(name))
res <-
StateT[V, SymTab, Int] {
s => vErrInt.map(s -> _)
}
} yield { res }
}
This StateT wraps functions of
type
SymTab => V[(SymTab,A)] ==
SymTab => /[Err, (SymTab,A)]
132. Handling errors
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
def pRef: P[Int] =
pName ^^ {
name => for {
symtab <- State.get
vErrInt =
symtab.get(name)
.toRightDisjunction(
NotDeclared(name))
res <-
StateT[V, SymTab, Int] {
s => vErrInt.map(s -> _)
}
} yield { res }
}
vErrInt is of type
/[Err, Int], we convert it to
/[Err, (SymTab, Int)] with map
133. Handling errors
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
def pRef: P[Int] =
pName ^^ {
name => for {
symtab <- State.get
vErrInt =
symtab.get(name)
.toRightDisjunction(
NotDeclared(name))
res <-
StateT[V, SymTab, Int] {
s => vErrInt.map(s -> _)
}
} yield { res }
}
This state transition will only
be made if vErrInt is a right
value. If not, all the
computation will return the
left value
134. Handling errors
def pRef: P[Int] =
pName ^^ {
name =>
for {
symtab <- State.get
} yield {
symtab(name)
}
}
def pRef: P[Int] =
pName ^^ {
name => for {
symtab <- State.get
vErrInt =
symtab.get(name)
.toRightDisjunction(
NotDeclared(name))
res <-
StateT[V, SymTab, Int] {
s => vErrInt.map(s -> _)
}
} yield { res }
}
Here is happening the
combination of both
monad effects
135. Handling errors
def pVar: P[Unit] =
"var" ~ pName ~ "=" ~ pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <- symtab.get(name) match {
case Some(v) =>
StateT[V, SymTab, Unit] { s =>
AlreadyDeclared(name, v).left
}
case None =>
State.put(symtab + (name->expr))
}
} yield { unit }
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
136. Handling errors
def pVar: P[Unit] =
"var" ~ pName ~ "=" ~ pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <- symtab.get(name) match {
case Some(v) =>
StateT[V, SymTab, Unit] { s =>
AlreadyDeclared(name, v).left
}
case None =>
State.put(symtab + (name->expr))
}
} yield { unit }
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
We check if the declaration
already exists
137. Handling errors
def pVar: P[Unit] =
"var" ~ pName ~ "=" ~ pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <- symtab.get(name) match {
case Some(v) =>
StateT[V, SymTab, Unit] { s =>
AlreadyDeclared(name, v).left
}
case None =>
State.put(symtab + (name->expr))
}
} yield { unit }
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
If exists, we construct a StateT with
a function returning a left. This stops
any further state transitions
138. Handling errors
def pVar: P[Unit] =
"var" ~ pName ~ "=" ~ pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <- symtab.get(name) match {
case Some(v) =>
StateT[V, SymTab, Unit] { s =>
AlreadyDeclared(name, v).left
}
case None =>
State.put(symtab + (name->expr))
}
} yield { unit }
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
If the value not exists, we proceed as
before, updating the state
139. Handling errors
def pVar: P[Unit] =
"var" ~ pName ~ "=" ~ pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <- symtab.get(name) match {
case Some(v) =>
StateT[V, SymTab, Unit] { s =>
AlreadyDeclared(name, v).left
}
case None =>
State.put(symtab + (name->expr))
}
} yield { unit }
}
def pVar: P[Unit] =
"var"~pName~"="~pExpr ^^ {
case _ ~ name ~ _ ~ exprS =>
for {
expr <- exprS
symtab <- State.get
unit <-State.put(
symtab+(name->expr)
)
} yield { unit }
}
In both cases we are returning
StateT[V, SymTab, Unit]
140. Now errors are handled properly
import Parser4._
parse(pL4, "var x = y;").map(_.run(Map.empty))
//> [1.11] parsed: -/(`y' not declared.)
parse(pL4, "var x = 1; x * x; var x = 2;").map(_.run(Map.empty))
//> [1.29] parsed: -/(`x' already declared with value `1'.)
141. Now errors are handled properly
import Parser4._
parse(pL4, "var x = y;").map(_.run(Map.empty))
//> [1.11] parsed: -/(`y' not declared.)
parse(pL4, "var x = 1; x * x; var x = 2;").map(_.run(Map.empty))
//> [1.29] parsed: -/(`x' already declared with value `1'.)
Both return left values