SlideShare a Scribd company logo
1 of 142
Download to read offline
@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
Initial language
Arithmetic expressions over integers
1;
2+2;
3*3+4*4*(1+2);
Functional parsing
Write a parser using “just functions” ...
… within some programming language
… in the form of some embedded DSL
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
Parser combinators
Scala has many parsing combinator libraries
Two common ones:
scala-parser-combinators
parboiled2
Parser combinators
Scala has many parsing combinator libraries
Two common ones:
scala-parser-combinators
parboiled2
The one we will be using
Parser combinators
Scala has many parsing combinator libraries
Two common ones:
scala-parser-combinators
parboiled2 Not really parser combinators ...
but very fast!
Parser combinators
Scala has many parsing combinator libraries
Two common ones:
scala-parser-combinators
parboiled2
New kid on the block: lihaoyi/fastparse
scala-parser-combinators
Part of the Scala standard library but distributed
separately
libraryDependencies +=
"org.scala-lang.modules" %%
"scala-parser-combinators" % "1.0.4"
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
scala-parser-combinators
import scala.util.parsing.combinator._
object BasicCombinators extends Parsers
with RegexParsers {
def pA : Parser[Char] = 'A'
def pB : Parser[Char] = 'B'
def pAlt: Parser[Char] = pA | pB
def pSeq: Parser[Char ~ Char] = pA ~ pB
def pRep: Parser[List[Char]] = rep(pA)
def pOpt: Parser[Option[Char]] = opt(pA)
}
import scala.util.parsing.combinator._
object BasicCombinators extends Parsers
with RegexParsers {
def pA : Parser[Char] = 'A'
def pB : Parser[Char] = 'B'
def pAlt: Parser[Char] = pA | pB
def pSeq: Parser[Char ~ Char] = pA ~ pB
def pRep: Parser[List[Char]] = rep(pA)
def pOpt: Parser[Option[Char]] = opt(pA)
}
scala-parser-combinators
Base type
import scala.util.parsing.combinator._
object BasicCombinators extends Parsers
with RegexParsers {
def pA : Parser[Char] = 'A'
def pB : Parser[Char] = 'B'
def pAlt: Parser[Char] = pA | pB
def pSeq: Parser[Char ~ Char] = pA ~ pB
def pRep: Parser[List[Char]] = rep(pA)
def pOpt: Parser[Option[Char]] = opt(pA)
}
scala-parser-combinators
Useful
extensions
import scala.util.parsing.combinator._
object BasicCombinators extends Parsers
with RegexParsers {
def pA : Parser[Char] = 'A'
def pB : Parser[Char] = 'B'
def pAlt: Parser[Char] = pA | pB
def pSeq: Parser[Char ~ Char] = pA ~ pB
def pRep: Parser[List[Char]] = rep(pA)
def pOpt: Parser[Option[Char]] = opt(pA)
}
scala-parser-combinators
alternative,
sequence,
repetition,
optionals ...
scala-parser-combinators
trait Parsers {
type Input
sealed trait ParseResult[+T]
trait Parser[+T] extends
(Input => ParseResult[T])
}
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
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)
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
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
Returning to our language
1;
2+2;
3*3+4*4*(1+2);
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 <~ ")"
}
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
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
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
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.
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
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
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
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
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
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
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
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
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 “;”
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?
Mini test
import Parser1._
parse(pExpr, "1") //> [1.2] parsed: 1
parse(pExpr, "2+2") //> [1.4] parsed: 4
parse(pExpr, "3*3+4*4*(1+2)") //> [1.14] parsed: 57
parse(pL1, """1;
|2+2;
|3*3+4*4*(1+2);""".stripMargin)
//> [3.15] parsed: List(1, 4, 57)
parse(pL1, "1; 2+2; 3*3+4*4*(1+2);")
//> [1.8] parsed: List(1, 4, 57)
Now …
Now …
What if we want to add variables?
What if we want to add variables?
We need to handle two more cases:
Assignation of variables
References to variables
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)
}
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)
}
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)
}
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
Enter scalaz.State[S,A]
We need to change the type of our parsers ...
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]
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]
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]
Parser[A]
Parser[State[S,A]]
Parser[State[SymTab, A]]
Parser[S[A]]
P[A]
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
type S[A] = State[SymTab,A]
Our state is the
symbol table
~Map[String, Int]
Parser[A]
Parser[State[S,A]]
Parser[State[SymTab, A]]
Parser[S[A]]
P[A]
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
type S[A] = State[SymTab,A]
Our state is the
symbol table
~Map[String, Int]
type P[A] = Parser[S[A]]
Enter scalaz.State[S,A]
… and change the parsers themselves a bit
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
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]]
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]
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
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]
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
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
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)
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
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)
}
}
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
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
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) }
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)
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))
… 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
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 }
}
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
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
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]
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]
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]
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]
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]
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]
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]
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]
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
Running the state
import Parser2._
parse(pExpr, "1") //> [1.2] parsed: scalaz.
package$State$$anon$3@7f560810
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
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
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
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]]
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
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
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)
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
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
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))
Now ...
Now ...
What if we reference an undeclared
variable?
parse(pL2, "var x = y;").map(_.run(Map.empty))
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)
...
Handling errors
If our computation can fail, then our types are
wrong. We can’t just return A
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
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
scalaz./
/[A,B] is the scalaz version of
scala.Either[A,B]
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'.")
Bad news
Bad news
It gets ugly ...
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 }
}
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
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]
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
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
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
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
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
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) }
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 }
}
...
Good news
Good news
Monad transformers
Monad transformers
The only thing I know about them, is that they
let you combine the effects of two monads, ...
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, …
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
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]]
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
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
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]
Combining State and /
And one final ingredient
val State = StateT.stateTMonadState[SymTab, V]
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.
Combining State and /
And one final ingredient
val State = StateT.stateTMonadState[SymTab, V]
Instead we have to instantiate one of this
“beasts”
Combining State and /
With that changes, our parser works as before
without any modification
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))
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)))
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.
Handling errors
Everything else is working the same, even our
exceptions when an undeclared variable is
referenced
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
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 }
}
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]
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
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)]
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
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
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
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 }
}
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
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
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
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]
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'.)
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
github.com/gerferra/parser-stateT
FIN

More Related Content

What's hot

Python Programming - XI. String Manipulation and Regular Expressions
Python Programming - XI. String Manipulation and Regular ExpressionsPython Programming - XI. String Manipulation and Regular Expressions
Python Programming - XI. String Manipulation and Regular Expressions
Ranel Padon
 
Programming in Computational Biology
Programming in Computational BiologyProgramming in Computational Biology
Programming in Computational Biology
AtreyiB
 

What's hot (11)

Python Programming - XI. String Manipulation and Regular Expressions
Python Programming - XI. String Manipulation and Regular ExpressionsPython Programming - XI. String Manipulation and Regular Expressions
Python Programming - XI. String Manipulation and Regular Expressions
 
Python (regular expression)
Python (regular expression)Python (regular expression)
Python (regular expression)
 
Programming in Computational Biology
Programming in Computational BiologyProgramming in Computational Biology
Programming in Computational Biology
 
Parboiled explained
Parboiled explainedParboiled explained
Parboiled explained
 
Python- Regular expression
Python- Regular expressionPython- Regular expression
Python- Regular expression
 
Parsing (Automata)
Parsing (Automata)Parsing (Automata)
Parsing (Automata)
 
Introduction to source{d} Engine and source{d} Lookout
Introduction to source{d} Engine and source{d} Lookout Introduction to source{d} Engine and source{d} Lookout
Introduction to source{d} Engine and source{d} Lookout
 
Python - Regular Expressions
Python - Regular ExpressionsPython - Regular Expressions
Python - Regular Expressions
 
Adv. python regular expression by Rj
Adv. python regular expression by RjAdv. python regular expression by Rj
Adv. python regular expression by Rj
 
.Net (F # ) Records, lists
.Net (F # ) Records, lists.Net (F # ) Records, lists
.Net (F # ) Records, lists
 
3.7 search text files using regular expressions
3.7 search text files using regular expressions3.7 search text files using regular expressions
3.7 search text files using regular expressions
 

Viewers also liked

Shallow parser for hindi language with an input from a transliterator
Shallow parser for hindi language with an input from a transliteratorShallow parser for hindi language with an input from a transliterator
Shallow parser for hindi language with an input from a transliterator
Shashank Shisodia
 

Viewers also liked (11)

D3 dhanalakshmi
D3 dhanalakshmiD3 dhanalakshmi
D3 dhanalakshmi
 
Shallow parser for hindi language with an input from a transliterator
Shallow parser for hindi language with an input from a transliteratorShallow parser for hindi language with an input from a transliterator
Shallow parser for hindi language with an input from a transliterator
 
Complex predicate meghaditya
Complex predicate meghadityaComplex predicate meghaditya
Complex predicate meghaditya
 
Domain Cartridge: Unsupervised Framework for Shallow Domain Ontology Construc...
Domain Cartridge: Unsupervised Framework for Shallow Domain Ontology Construc...Domain Cartridge: Unsupervised Framework for Shallow Domain Ontology Construc...
Domain Cartridge: Unsupervised Framework for Shallow Domain Ontology Construc...
 
OpenNLP demo
OpenNLP demoOpenNLP demo
OpenNLP demo
 
Compiler unit 2&3
Compiler unit 2&3Compiler unit 2&3
Compiler unit 2&3
 
Lexical analyzer
Lexical analyzerLexical analyzer
Lexical analyzer
 
Role-of-lexical-analysis
Role-of-lexical-analysisRole-of-lexical-analysis
Role-of-lexical-analysis
 
The sixth sense technology complete ppt
The sixth sense technology complete pptThe sixth sense technology complete ppt
The sixth sense technology complete ppt
 
Deep C
Deep CDeep C
Deep C
 
Big Data - 25 Amazing Facts Everyone Should Know
Big Data - 25 Amazing Facts Everyone Should KnowBig Data - 25 Amazing Facts Everyone Should Know
Big Data - 25 Amazing Facts Everyone Should Know
 

Similar to Transform your State \/ Err

TI1220 Lecture 9: Parsing & interpretation
TI1220 Lecture 9: Parsing & interpretationTI1220 Lecture 9: Parsing & interpretation
TI1220 Lecture 9: Parsing & interpretation
Eelco Visser
 
Five Languages in a Moment
Five Languages in a MomentFive Languages in a Moment
Five Languages in a Moment
Sergio Gil
 
Cypher inside out: Como a linguagem de pesquisas em grafo do Neo4j foi constr...
Cypher inside out: Como a linguagem de pesquisas em grafo do Neo4j foi constr...Cypher inside out: Como a linguagem de pesquisas em grafo do Neo4j foi constr...
Cypher inside out: Como a linguagem de pesquisas em grafo do Neo4j foi constr...
adrianoalmeida7
 
JDD2015: Functional programing and Event Sourcing - a pair made in heaven - e...
JDD2015: Functional programing and Event Sourcing - a pair made in heaven - e...JDD2015: Functional programing and Event Sourcing - a pair made in heaven - e...
JDD2015: Functional programing and Event Sourcing - a pair made in heaven - e...
PROIDEA
 

Similar to Transform your State \/ Err (20)

TI1220 Lecture 9: Parsing & interpretation
TI1220 Lecture 9: Parsing & interpretationTI1220 Lecture 9: Parsing & interpretation
TI1220 Lecture 9: Parsing & interpretation
 
Parsers Combinators in Scala, Ilya @lambdamix Kliuchnikov
Parsers Combinators in Scala, Ilya @lambdamix KliuchnikovParsers Combinators in Scala, Ilya @lambdamix Kliuchnikov
Parsers Combinators in Scala, Ilya @lambdamix Kliuchnikov
 
Implementing External DSLs Using Scala Parser Combinators
Implementing External DSLs Using Scala Parser CombinatorsImplementing External DSLs Using Scala Parser Combinators
Implementing External DSLs Using Scala Parser Combinators
 
Petitparser at the Deep into Smalltalk School 2011
Petitparser at the Deep into Smalltalk School 2011Petitparser at the Deep into Smalltalk School 2011
Petitparser at the Deep into Smalltalk School 2011
 
Five Languages in a Moment
Five Languages in a MomentFive Languages in a Moment
Five Languages in a Moment
 
Ch2
Ch2Ch2
Ch2
 
Use PEG to Write a Programming Language Parser
Use PEG to Write a Programming Language ParserUse PEG to Write a Programming Language Parser
Use PEG to Write a Programming Language Parser
 
Ch2 (1).ppt
Ch2 (1).pptCh2 (1).ppt
Ch2 (1).ppt
 
Cypher inside out: Como a linguagem de pesquisas em grafo do Neo4j foi constr...
Cypher inside out: Como a linguagem de pesquisas em grafo do Neo4j foi constr...Cypher inside out: Como a linguagem de pesquisas em grafo do Neo4j foi constr...
Cypher inside out: Como a linguagem de pesquisas em grafo do Neo4j foi constr...
 
List out of lambda
List out of lambdaList out of lambda
List out of lambda
 
JDD2015: Functional programing and Event Sourcing - a pair made in heaven - e...
JDD2015: Functional programing and Event Sourcing - a pair made in heaven - e...JDD2015: Functional programing and Event Sourcing - a pair made in heaven - e...
JDD2015: Functional programing and Event Sourcing - a pair made in heaven - e...
 
Logstash-Elasticsearch-Kibana
Logstash-Elasticsearch-KibanaLogstash-Elasticsearch-Kibana
Logstash-Elasticsearch-Kibana
 
Mastering Grammars with PetitParser
Mastering Grammars with PetitParserMastering Grammars with PetitParser
Mastering Grammars with PetitParser
 
Perl Basics with Examples
Perl Basics with ExamplesPerl Basics with Examples
Perl Basics with Examples
 
Frp2016 3
Frp2016 3Frp2016 3
Frp2016 3
 
Build a compiler in 2hrs - NCrafts Paris 2015
Build a compiler in 2hrs -  NCrafts Paris 2015Build a compiler in 2hrs -  NCrafts Paris 2015
Build a compiler in 2hrs - NCrafts Paris 2015
 
Scala. Introduction to FP. Monads
Scala. Introduction to FP. MonadsScala. Introduction to FP. Monads
Scala. Introduction to FP. Monads
 
7 Common mistakes in Go and when to avoid them
7 Common mistakes in Go and when to avoid them7 Common mistakes in Go and when to avoid them
7 Common mistakes in Go and when to avoid them
 
Scala the language matters
Scala the language mattersScala the language matters
Scala the language matters
 
Grammarware Memes
Grammarware MemesGrammarware Memes
Grammarware Memes
 

Recently uploaded

CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
VictorSzoltysek
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
Health
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
mohitmore19
 

Recently uploaded (20)

CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
10 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 202410 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 2024
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Exploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfExploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdf
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
LEVEL 5 - SESSION 1 2023 (1).pptx - PDF 123456
LEVEL 5   - SESSION 1 2023 (1).pptx - PDF 123456LEVEL 5   - SESSION 1 2023 (1).pptx - PDF 123456
LEVEL 5 - SESSION 1 2023 (1).pptx - PDF 123456
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
 

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
  • 2. Initial language Arithmetic expressions over integers 1; 2+2; 3*3+4*4*(1+2);
  • 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
  • 11. scala-parser-combinators import scala.util.parsing.combinator._ object BasicCombinators extends Parsers with RegexParsers { def pA : Parser[Char] = 'A' def pB : Parser[Char] = 'B' def pAlt: Parser[Char] = pA | pB def pSeq: Parser[Char ~ Char] = pA ~ pB def pRep: Parser[List[Char]] = rep(pA) def pOpt: Parser[Option[Char]] = opt(pA) }
  • 12. import scala.util.parsing.combinator._ object BasicCombinators extends Parsers with RegexParsers { def pA : Parser[Char] = 'A' def pB : Parser[Char] = 'B' def pAlt: Parser[Char] = pA | pB def pSeq: Parser[Char ~ Char] = pA ~ pB def pRep: Parser[List[Char]] = rep(pA) def pOpt: Parser[Option[Char]] = opt(pA) } scala-parser-combinators Base type
  • 13. import scala.util.parsing.combinator._ object BasicCombinators extends Parsers with RegexParsers { def pA : Parser[Char] = 'A' def pB : Parser[Char] = 'B' def pAlt: Parser[Char] = pA | pB def pSeq: Parser[Char ~ Char] = pA ~ pB def pRep: Parser[List[Char]] = rep(pA) def pOpt: Parser[Option[Char]] = opt(pA) } scala-parser-combinators Useful extensions
  • 14. import scala.util.parsing.combinator._ object BasicCombinators extends Parsers with RegexParsers { def pA : Parser[Char] = 'A' def pB : Parser[Char] = 'B' def pAlt: Parser[Char] = pA | pB def pSeq: Parser[Char ~ Char] = pA ~ pB def pRep: Parser[List[Char]] = rep(pA) def pOpt: Parser[Option[Char]] = opt(pA) } scala-parser-combinators alternative, sequence, repetition, optionals ...
  • 15. scala-parser-combinators trait Parsers { type Input sealed trait ParseResult[+T] trait Parser[+T] extends (Input => ParseResult[T]) }
  • 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
  • 20. Returning to our language 1; 2+2; 3*3+4*4*(1+2);
  • 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?
  • 36. Mini test import Parser1._ parse(pExpr, "1") //> [1.2] parsed: 1 parse(pExpr, "2+2") //> [1.4] parsed: 4 parse(pExpr, "3*3+4*4*(1+2)") //> [1.14] parsed: 57 parse(pL1, """1; |2+2; |3*3+4*4*(1+2);""".stripMargin) //> [3.15] parsed: List(1, 4, 57) parse(pL1, "1; 2+2; 3*3+4*4*(1+2);") //> [1.8] parsed: List(1, 4, 57)
  • 38. Now … What if we want to add variables?
  • 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
  • 44. Enter scalaz.State[S,A] We need to change the type of our parsers ...
  • 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]
  • 48. Parser[A] Parser[State[S,A]] Parser[State[SymTab, A]] Parser[S[A]] P[A] 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 type S[A] = State[SymTab,A] Our state is the symbol table ~Map[String, Int]
  • 49. Parser[A] Parser[State[S,A]] Parser[State[SymTab, A]] Parser[S[A]] P[A] 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 type S[A] = State[SymTab,A] Our state is the symbol table ~Map[String, Int] type P[A] = Parser[S[A]]
  • 50. Enter scalaz.State[S,A] … and change the parsers themselves a bit
  • 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
  • 96. scalaz./ /[A,B] is the scalaz version of scala.Either[A,B]
  • 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'.")
  • 99. Bad news It gets ugly ...
  • 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.
  • 126. Handling errors Everything else is working the same, even our exceptions when an undeclared variable is referenced
  • 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