Skip to content

Commit

Permalink
filter, based on selective (#101)
Browse files Browse the repository at this point in the history
* filter, based on selective functors

* Replace valueOr with a pattern match

* Missed a scalafmt

* Rudimentary Select generator

* More sophisticated Select generator

* Passing tests

* Conditionally apply the selective function

* select laws: not compiling yet

* some suggestions for selective

* Update core/shared/src/main/scala/cats/parse/Parser.scala

Co-authored-by: P. Oscar Boykin <boykin@pobox.com>
  • Loading branch information
rossabaker and johnynek committed Dec 8, 2020
1 parent 0fa68e6 commit a7fb824
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 0 deletions.
86 changes: 86 additions & 0 deletions core/shared/src/main/scala/cats/parse/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,21 @@ sealed abstract class Parser[+A] {
def map[B](fn: A => B): Parser[B] =
Parser.map(this)(fn)

/** If the predicate is not true, fail
* you may want .filter(fn).backtrack so if the filter fn
* fails you can fall through in an oneOf or orElse
*
* Without the backtrack, a failure of the function will
* be an arresting failure.
*/
def filter(fn: A => Boolean): Parser[A] = {
val leftUnit = Left(())
Parser.select(this.map { a =>
if (fn(a)) Right(a)
else leftUnit
})(Parser.Fail)
}

/** Dynamically construct the next parser based on the previously
* parsed value.
*
Expand Down Expand Up @@ -296,6 +311,16 @@ sealed abstract class Parser[+A] {
*/
sealed abstract class Parser1[+A] extends Parser[A] {

/** This method overrides `Parser#filter` to refine the return type.
*/
override def filter(fn: A => Boolean): Parser1[A] = {
val leftUnit = Left(())
Parser.select1(this.map { a =>
if (fn(a)) Right(a)
else leftUnit
})(Parser.Fail)
}

/** This method overrides `Parser#void` to refine the return type.
*/
override def void: Parser1[Unit] =
Expand Down Expand Up @@ -911,6 +936,22 @@ object Parser extends ParserInstances {
case _ => Impl.Map1(p, fn)
}

def select[A, B](p: Parser[Either[A, B]])(fn: Parser[A => B]): Parser[B] =
Impl
.Select(p, fn)
.map {
case Left((a, fn)) => fn(a)
case Right(b) => b
}

def select1[A, B](p: Parser1[Either[A, B]])(fn: Parser[A => B]): Parser1[B] =
Impl
.Select1(p, fn)
.map {
case Left((a, fn)) => fn(a)
case Right(b) => b
}

/** Standard monadic flatMap
* Avoid this function if possible. If you can
* instead use product, ~, *>, or <* use that.
Expand Down Expand Up @@ -1257,6 +1298,9 @@ object Parser extends ParserInstances {
case later => map2(pa, defer(later.value))(fn)
})

override def ap[A, B](pf: Parser1[A => B])(pa: Parser1[A]): Parser1[B] =
map(product(pf, pa)) { case (fn, a) => fn(a) }

def tailRecM[A, B](init: A)(fn: A => Parser1[Either[A, B]]): Parser1[B] =
tailRecM1(init)(fn)

Expand Down Expand Up @@ -1331,6 +1375,8 @@ object Parser extends ParserInstances {
case Map(p, _) =>
// we discard any allocations done by fn
unmap(p)
case Select(p, fn) =>
Select(p, unmap(fn))
case StringP(s) =>
// StringP is added privately, and only after unmap
s
Expand Down Expand Up @@ -1406,6 +1452,8 @@ object Parser extends ParserInstances {
case Map1(p, _) =>
// we discard any allocations done by fn
unmap1(p)
case Select1(p, fn) =>
Select1(p, unmap(fn))
case StringP1(s) =>
// StringP is added privately, and only after unmap
s
Expand Down Expand Up @@ -1722,6 +1770,41 @@ object Parser extends ParserInstances {
override def parseMut(state: State): B = Impl.map(parser, fn, state)
}

final def select[A, B, C](
pab: Parser[Either[A, B]],
pc: Parser[C],
state: State
): Either[(A, C), B] = {
val cap = state.capture
state.capture = true
val either = pab.parseMut(state)
state.capture = cap
if (state.error eq null)
either match {
case Left(a) =>
val c = pc.parseMut(state)
if (cap && (state.error eq null)) {
Left((a, c))
} else {
null
}
case r @ Right(_) => r.leftCast
}
else null
}

case class Select[A, B, C](pab: Parser[Either[A, B]], pc: Parser[C])
extends Parser[Either[(A, C), B]] {
override def parseMut(state: State): Either[(A, C), B] =
Impl.select(pab, pc, state)
}

case class Select1[A, B, C](pab: Parser1[Either[A, B]], pc: Parser[C])
extends Parser1[Either[(A, C), B]] {
override def parseMut(state: State): Either[(A, C), B] =
Impl.select(pab, pc, state)
}

final def flatMap[A, B](parser: Parser[A], fn: A => Parser[B], state: State): B = {
// we can't void before flatMap unfortunately, because
// we need to be able to produce the next parser
Expand Down Expand Up @@ -2087,6 +2170,9 @@ abstract class ParserInstances {
case later => map2(pa, defer(later.value))(fn)
})

override def ap[A, B](pf: Parser[A => B])(pa: Parser[A]): Parser[B] =
map(product(pf, pa)) { case (fn, a) => fn(a) }

def flatMap[A, B](fa: Parser[A])(fn: A => Parser[B]): Parser[B] =
Parser.flatMap(fa)(fn)

Expand Down
86 changes: 86 additions & 0 deletions core/shared/src/test/scala/cats/parse/ParserTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,32 @@ object ParserGen {
val fn: A => F[B]
}

def selected(ga: Gen[GenT[Parser]]): Gen[GenT[Parser]] =
Gen.zip(pures, pures).flatMap { case (genRes1, genRes2) =>
val genPR: Gen[Parser[Either[genRes1.A, genRes2.A]]] = {
ga.flatMap { init =>
val mapFn: Gen[init.A => Either[genRes1.A, genRes2.A]] =
Gen.function1(Gen.either(genRes1.fa, genRes2.fa))(init.cogen)
mapFn.map { fn =>
init.fa.map(fn)
}
}
}

val gfn: Gen[Parser[genRes1.A => genRes2.A]] =
ga.flatMap { init =>
val mapFn: Gen[init.A => (genRes1.A => genRes2.A)] =
Gen.function1(Gen.function1(genRes2.fa)(genRes1.cogen))(init.cogen)
mapFn.map { fn =>
init.fa.map(fn)
}
}

Gen.zip(genPR, gfn).map { case (pab, fn) =>
GenT(Parser.select(pab)(fn))(genRes2.cogen)
}
}

def flatMapped(ga: Gen[GenT[Parser]]): Gen[GenT[Parser]] =
Gen.zip(ga, pures).flatMap { case (parser, genRes) =>
val genPR: Gen[Parser[genRes.A]] = {
Expand Down Expand Up @@ -313,6 +339,32 @@ object ParserGen {
}
}

def selected1(ga1: Gen[GenT[Parser1]], ga0: Gen[GenT[Parser]]): Gen[GenT[Parser1]] =
Gen.zip(pures, pures).flatMap { case (genRes1, genRes2) =>
val genPR1: Gen[Parser1[Either[genRes1.A, genRes2.A]]] = {
ga1.flatMap { init =>
val mapFn: Gen[init.A => Either[genRes1.A, genRes2.A]] =
Gen.function1(Gen.either(genRes1.fa, genRes2.fa))(init.cogen)
mapFn.map { fn =>
init.fa.map(fn)
}
}
}

val gfn: Gen[Parser[genRes1.A => genRes2.A]] =
ga0.flatMap { init =>
val mapFn: Gen[init.A => (genRes1.A => genRes2.A)] =
Gen.function1(Gen.function1(genRes2.fa)(genRes1.cogen))(init.cogen)
mapFn.map { fn =>
init.fa.map(fn)
}
}

Gen.zip(genPR1, gfn).map { case (pab, fn) =>
GenT(Parser.select1(pab)(fn))(genRes2.cogen)
}
}

def flatMapped1(ga: Gen[GenT[Parser]], ga1: Gen[GenT[Parser1]]): Gen[GenT[Parser1]] =
Gen.zip(ga, ga1, pures).flatMap { case (parser, parser1, genRes) =>
val genPR: Gen[Parser1[genRes.A]] = {
Expand Down Expand Up @@ -403,6 +455,7 @@ object ParserGen {
(1, rec.map { gen => GenT(!gen.fa) }),
(1, Gen.lzy(gen1.map(rep(_)))),
(1, rec.flatMap(mapped(_))),
(1, rec.flatMap(selected(_))),
(1, tailRecM(Gen.lzy(gen1))),
(1, Gen.choose(0, 10).map { l => GenT(Parser.length(l)) }),
(1, flatMapped(rec)),
Expand All @@ -426,6 +479,7 @@ object ParserGen {
(2, rec.map(backtrack1(_))),
(1, rec.map(defer1(_))),
(1, rec.map(rep1(_))),
(1, selected1(rec, gen)),
(1, rec.flatMap(mapped1(_))),
(1, flatMapped1(gen, rec)),
(1, tailRecM1(rec)),
Expand Down Expand Up @@ -1561,4 +1615,36 @@ class ParserTest extends munit.ScalaCheckSuite {
}
}

/*
property("select(pa.map(Left(_)))(pf) == (pa, pf).mapN((a, fn) => fn(a))") {
forAll(ParserGen.gen, ParserGen.gen, Arbitrary.arbitrary[String]) { (genP, genRes, str) =>
val pa = genP.fa
val pf = null: Parser[genP.A => genRes.A]
assertEquals(Parser.select(pa.map(Left(_)))(pf).parse(str), pf.ap(pa).parse(str))
}
}
*/

property("select(pa.map(Right(_)))(pf) == pa") {
forAll(ParserGen.gen, ParserGen.gen, Arbitrary.arbitrary[String]) { (genP, genRes, str) =>
val pa = genRes.fa
val pf: Parser[genP.A => genRes.A] = Parser.fail
assertEquals(Parser.select(pa.map(Right(_)))(pf).parse(str), pa.parse(str))
}
}

property("p.filter(_ => true) == p") {
forAll(ParserGen.gen, Arbitrary.arbitrary[String]) { (genP, str) =>
val res0 = genP.fa.filter(_ => true).parse(str)
val res1 = genP.fa.parse(str)
assertEquals(res0, res1)
}
}

property("p.filter(_ => false) fails") {
forAll(ParserGen.gen, Arbitrary.arbitrary[String]) { (genP, str) =>
val res = genP.fa.filter(_ => false).parse(str)
assert(res.isLeft)
}
}
}

0 comments on commit a7fb824

Please sign in to comment.