From 48537f66f3f6c52d632f914f7690da266a194218 Mon Sep 17 00:00:00 2001 From: sergei-shabanau Date: Tue, 12 Feb 2019 15:01:21 -0500 Subject: [PATCH] #26 - Syntax and examples for sequential composition of codecs --- .../scala/scalaz/parsers/ParserSyntax.scala | 2 +- src/main/scala/scalaz/parsers/Parsing.scala | 41 +++++++++++- .../scalaz/parsers/SimplestCodecSpec.scala | 64 ++++++++++++++++--- 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/main/scala/scalaz/parsers/ParserSyntax.scala b/src/main/scala/scalaz/parsers/ParserSyntax.scala index fa34a62..704d68c 100644 --- a/src/main/scala/scalaz/parsers/ParserSyntax.scala +++ b/src/main/scala/scalaz/parsers/ParserSyntax.scala @@ -22,7 +22,7 @@ trait ParserSyntax[P[_], F[_], G[_]] { def delay[A](pa: => P[A]): P[A] - implicit class ParserOps[A](p: P[A]) { + implicit final class ParserOps[A](p: P[A]) { def /\ [B](other: P[B])(implicit P: ProductFunctor[P]): P[A /\ B] = and(p, other) diff --git a/src/main/scala/scalaz/parsers/Parsing.scala b/src/main/scala/scalaz/parsers/Parsing.scala index a5ecc8f..8d55e02 100644 --- a/src/main/scala/scalaz/parsers/Parsing.scala +++ b/src/main/scala/scalaz/parsers/Parsing.scala @@ -109,7 +109,7 @@ sealed trait Parsing[F[_], G[_]] { } trait EquivSyntax { - implicit class ToEquivOps[A, B](self: Equiv[A, B]) { + implicit final class ToEquivOps[A, B](self: Equiv[A, B]) { def >>> [C](that: Equiv[B, C]): Equiv[A, C] = Equiv( @@ -175,6 +175,15 @@ sealed trait Parsing[F[_], G[_]] { case class Codec[I, A](eq: Equiv[I, (I, A)]) { self => import syntax._ + def parse(i: I): F[(I, A)] = + eq.to(i) + + def print(a: A, initial: I): G[I] = + eq.from(initial -> a) + + def print0(a: A)(implicit M: Monoid[I]): G[I] = + eq.from(M.mempty -> a) + // ProductFunctor functionality def ~ [B](that: Codec[I, B]): Codec[I, (A, B)] = Codec( @@ -207,7 +216,7 @@ sealed trait Parsing[F[_], G[_]] { ) ) - // IsoFunctor functionality + // IsoMonad functionality def ∘ [B](equiv: Equiv[A, B]): Codec[I, B] = Codec( self.eq >>> Equiv[(I, A), (I, B)]( @@ -228,4 +237,32 @@ sealed trait Parsing[F[_], G[_]] { def pure[I, A](a: A): Codec[I, A] = Codec(Equiv.lift[I, (I, A)]((_, a), _._1)) } + + implicit final class CodecChainSyntax[I, A](self: Codec[I, A]) { + // I => (I, A) then A => (A, B) is + // I => ((I, A), B) + // cannot be expressed with Codec (I position is defined by Equiv) + + // A => (A, B) + // cannot be implemented as Codec (input must be I) + + // I => (I, (A, B)) + // possible but clashes with product in return type + + def andThen[B](other: Codec[A, B]): Codec[I, (A, B)] = + Codec( + new Equiv[I, (I, (A, B))] { + override def to: I => F[(I, (A, B))] = + AB.compose[I, (I, A), (I, (A, B))]( + AB.second[A, (A, B), I](other.eq.to), + self.eq.to + ) + override def from: ((I, (A, B))) => G[I] = + BA.compose[(I, (A, B)), (I, A), I]( + self.eq.from, + BA.second[(A, B), A, I](other.eq.from) + ) + } + ) + } } diff --git a/src/test/scala/scalaz/parsers/SimplestCodecSpec.scala b/src/test/scala/scalaz/parsers/SimplestCodecSpec.scala index 7c406f6..d866a5c 100644 --- a/src/test/scala/scalaz/parsers/SimplestCodecSpec.scala +++ b/src/test/scala/scalaz/parsers/SimplestCodecSpec.scala @@ -64,12 +64,12 @@ class SimplestCodecSpec extends Specification { { case Sum(e1, e2) => e1 -> ('+' -> e2) } ) - type C[A] = Codec[List[Char], A] + type C[A] = Codec[String, A] val char: C[Char] = Codec( Equiv.liftF( - { case h :: t => Some(t -> h); case Nil => None }, - { case (l, c) => Some(c :: l) } + s => s.headOption.map(s.drop(1) -> _), + { case (s, c) => Some(s + c) } ) ) @@ -88,11 +88,11 @@ class SimplestCodecSpec extends Specification { lazy val expression: C[Expression] = case1 } - def parse(s: String): Option[(List[Char], Syntax.Expression)] = - Example.expression.eq.to(s.toCharArray.toList) + def parse(s: String): Option[(String, Syntax.Expression)] = + Example.expression.parse(s) def print(e: Syntax.Expression): Option[String] = - Example.expression.eq.from((Nil, e)).map(_.reverse.toArray).map(String.valueOf) + Example.expression.print0(e) "Simplest parser" should { import Syntax._ @@ -102,11 +102,11 @@ class SimplestCodecSpec extends Specification { } "parse a digit into a literal" in { - parse("5") must_=== Some(Nil -> Constant(5)) + parse("5") must_=== Some("" -> Constant(5)) } "not parse two digits (because it's indeed simplest)" in { - parse("55") must_=== Some(List('5') -> Constant(5)) + parse("55") must_=== Some("5" -> Constant(5)) } "not parse a letter and indicate failure" in { @@ -118,11 +118,11 @@ class SimplestCodecSpec extends Specification { } "parse sum of 2 numbers" in { - parse("5+6") must_=== Some(Nil -> Sum(Constant(5), Constant(6))) + parse("5+6") must_=== Some("" -> Sum(Constant(5), Constant(6))) } "parse sum of 3 numbers" in { - parse("5+6+7") must_=== Some(Nil -> Sum(Sum(Constant(5), Constant(6)), Constant(7))) + parse("5+6+7") must_=== Some("" -> Sum(Sum(Constant(5), Constant(6)), Constant(7))) } } @@ -147,4 +147,48 @@ class SimplestCodecSpec extends Specification { print(Sum(Sum(Constant(1), Constant(2)), Sum(Constant(3), Constant(4)))) must_=== None } } + + object ExampleCodecComposition { + import Syntax._ + import Example._ + import parsing.Codec + import parsing.Equiv + + val expressionToInt: Codec[Expression, Int] = Codec[Expression, Int]( + Equiv.liftF( + e => Some((e, 1)), + { case (e, i) => Some(Sum(e, Constant(i))) } + ) + ) + } + + "Running chain of codecs" >> { + import Syntax._ + import Example._ + import ExampleCodecComposition._ + + "codec composition" in { + import parsing.CodecChainSyntax // special import required + val chain: C[(Syntax.Expression, Int)] = expression.andThen(expressionToInt) + + chain.parse("567") must_=== Some(("67", (Constant(5), 1))) + chain.print((Constant(765), 1), "") must_=== Some("7+1") + } + + "parser composition with Monad[F]" in { + val res = for { + (r1, exp) <- expression.parse("567") + (r2, int) <- expressionToInt.parse(exp) + } yield ((r1, r2), int) + res must_=== Some((("67", Constant(5)), 1)) + } + + "printer composition with Monad[G]" in { + val res = for { + exp <- expressionToInt.print(1, Constant(0)) + str <- expression.print(exp, "") + } yield str + res must_=== Some("0+1") + } + } }