Skip to content
This repository has been archived by the owner on Jun 15, 2020. It is now read-only.

Commit

Permalink
Refactor FunctionalBuilder to improve Formatting errors
Browse files Browse the repository at this point in the history
FunctionalBuilder is splitted into FunctorSyntax, ContravariantSyntax, and
InvariantSyntax, used respectivery for the Rule, Write and Format ~ syntax. As
a result, error messages are much nicer, and the trailing _ after the .apply
and .unapply is no longer mendatory.
  • Loading branch information
OlivierBlanvillain authored and jto committed Feb 11, 2016
1 parent 6509c2b commit 357b877
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 91 deletions.
162 changes: 95 additions & 67 deletions project/Boilerplate.scala
Expand Up @@ -26,25 +26,36 @@ object Boilerplate {
val minArity = 2
val maxArity = 22

val templates: Seq[Template] = List(
InvariantSyntax,
FunctorSyntax,
ContravariantSyntax
)

/** Returns a seq of the generated files. As a side-effect, it actually generates them... */
def gen(dir: File) = {
val template = FunctionalBuilder
val tgtFile = template.filename(dir)
IO.write(tgtFile, template.body)
Seq(tgtFile)
}
def gen(dir: File) =
for(template <- templates) yield {
val tgtFile = template.filename(dir / "jto" / "validation")
IO.write(tgtFile, template.body)
tgtFile
}

class TemplateVals(val arity: Int) {
val synTypes = (0 until arity) map (n => s"A$n")
val synVals = (0 until arity) map (n => s"a$n")
val synTypedVals = (synVals zip synTypes) map { case (v,t) => v + ": " + t}
val `A..N` = synTypes.mkString(", ")
val `a..n` = synVals.mkString(", ")
val `_.._` = Seq.fill(arity)("_").mkString(", ")
val `(A..N)` = if (arity == 1) "Tuple1[A]" else synTypes.mkString("(", ", ", ")")
val `(_.._)` = if (arity == 1) "Tuple1[_]" else Seq.fill(arity)("_").mkString("(", ", ", ")")
val `(a..n)` = if (arity == 1) "Tuple1(a)" else synVals.mkString("(", ", ", ")")
val `a:A..n:N` = synTypedVals mkString ", "
val synTypes = (0 until arity) map (n => s"A$n")
val synVals = (0 until arity) map (n => s"a$n")
val synTypedVals = (synVals zip synTypes) map { case (v,t) => v + ": " + t}
val `A..N` = synTypes.mkString(", ")
val `a..n` = synVals.mkString(", ")
val `_.._` = Seq.fill(arity)("_").mkString(", ")
val `(A..N)` = if (arity == 1) "Tuple1[A]" else synTypes.mkString("(", ", ", ")")
val `(_.._)` = if (arity == 1) "Tuple1[_]" else Seq.fill(arity)("_").mkString("(", ", ", ")")
val `(a..n)` = if (arity == 1) "Tuple1(a)" else synVals.mkString("(", ", ", ")")
val `a:A..n:N` = synTypedVals mkString ", "
val `a~n` = synVals.mkString(" ~ ")
val `A~N` = synTypes.mkString(" ~ ")
val `A~N-1` = (0 until arity - 1).map(n => s"A$n").mkString(" ~ ")
val `a._1..a._N` = (1 to arity) map (n => s"a._$n") mkString ", "
val `new ~(.., n)` = synVals.reduce[String] { case (acc, el) => s"new ~($acc, $el)" }
}

trait Template {
Expand All @@ -61,7 +72,6 @@ object Boilerplate {
}
}


/*
Blocks in the templates below use a custom interpolator, combined with post-processing to produce the body
Expand All @@ -77,77 +87,95 @@ object Boilerplate {
The block otherwise behaves as a standard interpolated string with regards to variable substitution.
*/

object FunctionalBuilder extends Template {
def filename(root: File) = root / "jto" / "validation" / "FunctionalBuilder.scala"
object InvariantSyntax extends Template {
def filename(root: File) = root / "InvariantSyntax.scala"

def content(tv: TemplateVals) = {
import tv._

val `a~n` = synVals.mkString(" ~ ")
val `A~N` = synTypes.mkString(" ~ ")
val `A~N-1` = (0 until arity - 1).map(n => s"A$n").mkString(" ~ ")
val `a._1..a._N` = (1 to arity) map (n => s"a._$n") mkString ", "
val `new ~(.., n)` = synVals.reduce[String] { case (acc, el) => s"new ~($acc, $el)" }

val next = if (arity + 1 <= maxArity)
s"def ~[A$arity](m3: M[A$arity]) = new CanBuild${arity+1}[${`A..N`}, A$arity](canBuild(m1, m2), m3)"
else
""

val and = if (arity + 1 <= maxArity)
s"def and[A$arity](m3: M[A$arity]) = this.~(m3)"
else
""
val next = if (arity >= maxArity) "" else
s"def ~[A$arity](m3: M[A$arity]) = new InvariantSyntax${arity+1}[${`A..N`}, A$arity](combine(m1, m2), m3)"

block"""
|package jto.validation
|
|import cats.Functor
|import cats.functor._
|import cats.functor.Invariant
|
|case class ~[A, B](_1: A, _2: B)
|class InvariantSyntax[M[_]](combine: SyntaxCombine[M]) {
|
|trait FunctionalCanBuild[M[_]] {
| def apply[A, B](ma: M[A], mb: M[B]): M[A ~ B]
- class InvariantSyntax$arity[${`A..N`}](m1: M[${`A~N-1`}], m2: M[A${arity-1}]) {
- $next
-
- def apply[B](f1: (${`A..N`}) => B, f2: B => Option[(${`A..N`})])(implicit fu: Invariant[M]): M[B] =
- fu.imap[${`A~N`}, B](
- combine(m1, m2))({ case ${`a~n`} => f1(${`a..n`}) })(
- (b: B) => { val (${`a..n`}) = f2(b).get; ${`new ~(.., n)`} }
- )
-
- def tupled(implicit fu: Invariant[M]): M[(${`A..N`})] =
- apply[(${`A..N`})]({ (${`a:A..n:N`}) => (${`a..n`}) }, { (a: (${`A..N`})) => Some((${`a._1..a._N`})) })
- }
-
|}
"""
}
}

object FunctorSyntax extends Template {
def filename(root: File) = root / "FunctorSyntax.scala"

def content(tv: TemplateVals) = {
import tv._

val next = if (arity >= maxArity) "" else
s"def ~[A$arity](m3: M[A$arity]) = new FunctorSyntax${arity+1}[${`A..N`}, A$arity](combine(m1, m2), m3)"

block"""
|package jto.validation
|
|class FunctionalBuilderOps[M[_], A](ma: M[A])(implicit fcb: FunctionalCanBuild[M]) {
| def ~[B](mb: M[B]): FunctionalBuilder[M]#CanBuild2[A, B] = {
| val b = new FunctionalBuilder(fcb)
| new b.CanBuild2[A, B](ma, mb)
| }
|}
|import cats.Functor
|
|class FunctionalBuilder[M[_]](canBuild: FunctionalCanBuild[M]) {
|class FunctorSyntax[M[_]](combine: SyntaxCombine[M]) {
|
- class CanBuild$arity[${`A..N`}](m1: M[${`A~N-1`}], m2: M[A${arity-1}]) {
- class FunctorSyntax${arity}[${`A..N`}](m1: M[${`A~N-1`}], m2: M[A${arity-1}]) {
- $next
-
- $and
-
- def apply[B](f: (${`A..N`}) => B)(implicit fu: Functor[M]): M[B] =
- fu.map[${`A~N`}, B](canBuild(m1, m2))({ case ${`a~n`} => f(${`a..n`}) })
-
- def apply[B](f: B => (${`A..N`}))(implicit fu: Contravariant[M], d: DummyImplicit): M[B] =
- fu.contramap(canBuild(m1, m2))((b: B) => { val (${`a..n`}) = f(b); ${`new ~(.., n)`} })
- fu.map[${`A~N`}, B](combine(m1, m2))({ case ${`a~n`} => f(${`a..n`}) })
-
- def unlifted[B](f: B => Option[(${`A..N`})])(implicit fu: Contravariant[M]): M[B] =
- fu.contramap(canBuild(m1, m2))((b: B) => { val (${`a..n`}) = f(b).get; ${`new ~(.., n)`} })
- def tupled(implicit fu: Functor[M]): M[(${`A..N`})] =
- apply[(${`A..N`})]({ (${`a:A..n:N`}) => (${`a..n`}) })
- }
-
- def apply[B](f1: (${`A..N`}) => B, f2: B => (${`A..N`}))(implicit fu: Invariant[M], d: DummyImplicit): M[B] =
- fu.imap[${`A~N`}, B](
- canBuild(m1, m2))({ case ${`a~n`} => f1(${`a..n`}) })(
- (b: B) => { val (${`a..n`}) = f2(b); ${`new ~(.., n)`} }
- )
|}
"""
}
}

object ContravariantSyntax extends Template {
def filename(root: File) = root / "ContravariantSyntax.scala"

def content(tv: TemplateVals) = {
import tv._

val next = if (arity >= maxArity) "" else
s"def ~[A$arity](m3: M[A$arity]) = new ContravariantSyntax${arity+1}[${`A..N`}, A$arity](combine(m1, m2), m3)"

block"""
|package jto.validation
|
|import cats.functor.Contravariant
|
|class ContravariantSyntax[M[_]](combine: SyntaxCombine[M]) {
|
- class ContravariantSyntax${arity}[${`A..N`}](m1: M[${`A~N-1`}], m2: M[A${arity-1}]) {
- $next
-
- def unlifted[B](f1: (${`A..N`}) => B, f2: B => Option[(${`A..N`})])(implicit fu: Invariant[M]): M[B] =
- fu.imap[${`A~N`}, B](
- canBuild(m1, m2))({ case ${`a~n`} => f1(${`a..n`}) })(
- (b: B) => { val (${`a..n`}) = f2(b).get; ${`new ~(.., n)`} }
- )
- def apply[B](f: B => Option[(${`A..N`})])(implicit fu: Contravariant[M]): M[B] =
- fu.contramap(combine(m1, m2))((b: B) => { val (${`a..n`}) = f(b).get; ${`new ~(.., n)`} })
-
- def tupled(implicit fu: Invariant[M]): M[(${`A..N`})] =
- apply[(${`A..N`})]({ (${`a:A..n:N`}) => (${`a..n`}) }, { (a: (${`A..N`})) => (${`a._1..a._N`}) })
- def tupled(implicit fu: Contravariant[M]): M[(${`A..N`})] =
- apply[(${`A..N`})]({ (a: (${`A..N`})) => Some((${`a._1..a._N`})) })
- }
-
|}
Expand Down
8 changes: 4 additions & 4 deletions validation-core/src/main/scala/Format.scala
Expand Up @@ -25,12 +25,12 @@ object Format {
Format[IR, IW, B](Rule.toRule(fa).map(f1), Write.toWrite(fa).contramap(f2))
}

implicit def functionalCanBuildFormat[IR, IW : Monoid](implicit rcb: FunctionalCanBuild[Rule[IR, ?]], wcb: FunctionalCanBuild[Write[?, IW]]): FunctionalCanBuild[Format[IR, IW, ?]] =
new FunctionalCanBuild[Format[IR, IW, ?]] {
implicit def formatSyntaxCombine[IR, IW : Monoid](implicit rcb: SyntaxCombine[Rule[IR, ?]], wcb: SyntaxCombine[Write[?, IW]]): SyntaxCombine[Format[IR, IW, ?]] =
new SyntaxCombine[Format[IR, IW, ?]] {
def apply[A, B](fa: Format[IR, IW, A], fb: Format[IR, IW, B]): Format[IR, IW, A ~ B] =
Format[IR, IW, A ~ B](rcb(Rule.toRule(fa), Rule.toRule(fb)), wcb(Write.toWrite(fa), Write.toWrite(fb)))
}

implicit def fboFormat[IR, IW : Monoid, O](f: Format[IR, IW, O])(implicit fcb: FunctionalCanBuild[Format[IR, IW, ?]]): FunctionalBuilderOps[Format[IR, IW, ?], O] =
toFunctionalBuilderOps[Format[IR, IW, ?], O](f)(fcb)
implicit def formatInvariantSyntaxObs[IR, IW : Monoid, O](f: Format[IR, IW, O])(implicit fcb: SyntaxCombine[Format[IR, IW, ?]]): InvariantSyntaxObs[Format[IR, IW, ?], O] =
new InvariantSyntaxObs[Format[IR, IW, ?], O](f)(fcb)
}
4 changes: 2 additions & 2 deletions validation-core/src/main/scala/MappingMacros.scala
Expand Up @@ -101,10 +101,10 @@ object MappingMacros {
val body = (writes: @unchecked) match {
case w1 :: w2 :: ts =>
val typeApply = ts.foldLeft(q"$w1 ~ $w2") { (t1, t2) => q"$t1 ~ $t2" }
q"($typeApply).apply(scala.Function.unlift($unapply(_)): $t)"
q"($typeApply).apply($unapply(_))"

case w1 :: Nil =>
q"$w1.contramap(scala.Function.unlift($unapply(_)): $t)"
q"$w1.contramap(Function.unlift($unapply(_)): $t)"
}

// XXX: recursive values need the user to use explcitly typed implicit val
Expand Down
8 changes: 4 additions & 4 deletions validation-core/src/main/scala/Rule.scala
Expand Up @@ -145,12 +145,12 @@ object Rule {
def ap[A, B](ma: Rule[I, A])(mf: Rule[I, A => B]): Rule[I, B] = ma.ap(mf)
}

implicit def functionalCanBuildRule[I]: FunctionalCanBuild[Rule[I, ?]] =
new FunctionalCanBuild[Rule[I, ?]] {
implicit def ruleSyntaxCombine[I]: SyntaxCombine[Rule[I, ?]] =
new SyntaxCombine[Rule[I, ?]] {
def apply[A, B](a: Rule[I, A], b: Rule[I, B]): Rule[I, A ~ B] =
b.ap(a.map(a => c => new ~(a, c)))
}

implicit def fboRule[I, O](r: Rule[I, O]): FunctionalBuilderOps[Rule[I, ?], O] =
toFunctionalBuilderOps[Rule[I, ?], O](r)
implicit def ruleFunctorSyntaxObs[I, O](r: Rule[I, O])(implicit fcb: SyntaxCombine[Rule[I, ?]]): FunctorSyntaxObs[Rule[I, ?], O] =
new FunctorSyntaxObs[Rule[I, ?], O](r)(fcb)
}
28 changes: 28 additions & 0 deletions validation-core/src/main/scala/SyntaxObs.scala
@@ -0,0 +1,28 @@
package jto.validation

case class ~[A, B](_1: A, _2: B)

trait SyntaxCombine[M[_]] {
def apply[A, B](ma: M[A], mb: M[B]): M[A ~ B]
}

class InvariantSyntaxObs[M[_], A](ma: M[A])(implicit combine: SyntaxCombine[M]) {
def ~[B](mb: M[B]): InvariantSyntax[M]#InvariantSyntax2[A, B] = {
val b = new InvariantSyntax(combine)
new b.InvariantSyntax2[A, B](ma, mb)
}
}

class FunctorSyntaxObs[M[_], A](ma: M[A])(implicit combine: SyntaxCombine[M]) {
def ~[B](mb: M[B]): FunctorSyntax[M]#FunctorSyntax2[A, B] = {
val b = new FunctorSyntax(combine)
new b.FunctorSyntax2[A, B](ma, mb)
}
}

class ContravariantSyntaxObs[M[_], A](ma: M[A])(implicit combine: SyntaxCombine[M]) {
def ~[B](mb: M[B]): ContravariantSyntax[M]#ContravariantSyntax2[A, B] = {
val b = new ContravariantSyntax(combine)
new b.ContravariantSyntax2[A, B](ma, mb)
}
}
14 changes: 7 additions & 7 deletions validation-core/src/main/scala/Write.scala
Expand Up @@ -58,14 +58,14 @@ object Write {
def contramap[A, B](wa: Write[A, O])(f: B => A): Write[B, O] =
wa.contramap(f)
}
implicit def functionalCanBuildWrite[O](implicit m: Monoid[O]): FunctionalCanBuild[Write[?, O]] =
new FunctionalCanBuild[Write[?, O]] {
def apply[A, B](wa: Write[A, O], wb: Write[B, O]): Write[A ~ B, O] = Write[A ~ B, O] {

implicit def writeSyntaxCombine[O](implicit m: Monoid[O]): SyntaxCombine[Write[?, O]] =
new SyntaxCombine[Write[?, O]] {
def apply[A, B](wa: Write[A, O], wb: Write[B, O]): Write[A ~ B, O] = Write[A ~ B, O] {
case a ~ b => m.combine(wa.writes(a), wb.writes(b))
}
}
implicit def fboWrite[I, O : Monoid](w: Write[I, O]): FunctionalBuilderOps[Write[?, O], I] =
toFunctionalBuilderOps[Write[?, O], I](w)

implicit def writeContravariantSyntaxObs[I, O : Monoid](w: Write[I, O])(implicit fcb: SyntaxCombine[Write[?, O]]): ContravariantSyntaxObs[Write[?, O], I] =
new ContravariantSyntaxObs[Write[?, O], I](w)(fcb)
}
7 changes: 0 additions & 7 deletions validation-core/src/main/scala/package.scala
Expand Up @@ -28,13 +28,6 @@ package object validation {
type Invalid[+E] = cats.data.Validated.Invalid[E]
val Invalid = cats.data.Validated.Invalid

implicit def validatedBackcompat[E, A](va: Validated[Seq[E], A]): VABackCompat[E, A] =
new VABackCompat[E, A] {
val v = va
}

implicit def toFunctionalBuilderOps[M[_], A](a: M[A])(implicit fcb: FunctionalCanBuild[M]): FunctionalBuilderOps[M, A] = new FunctionalBuilderOps[M, A](a)(fcb)

implicit def applySyntaxU[FA](fa: FA)(implicit U: Unapply[Apply, FA]): ApplyOps[U.M, U.A] = {
object As extends ApplySyntax1
As.applySyntaxU(fa)
Expand Down

0 comments on commit 357b877

Please sign in to comment.