Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add F[TupleN] syntax #313

Merged
merged 5 commits into from
Feb 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ lazy val cross = crossProject(JSPlatform, JVMPlatform)
case _ => Nil
}
},
mimaPreviousArtifacts ~= { _.filterNot(_.revision == "1.0.1") }
mimaPreviousArtifacts ~= { _.filterNot(_.revision == "1.0.1") },
Compile / sourceGenerators += (Compile / sourceManaged).map(Boilerplate.gen).taskValue
)
.jsSettings(
crossScalaVersions := (ThisBuild / crossScalaVersions).value.filter(_.startsWith("2"))
Expand Down
1 change: 1 addition & 0 deletions js/src/main/scala/mouse/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package object mouse extends MouseFunctions {
object feither extends FEitherSyntax
object fnested extends FNestedSyntax
object foption extends FOptionSyntax
object ftuple extends FTupleSyntax
object int extends IntSyntax
object long extends LongSyntax
object map extends MapSyntax
Expand Down
1 change: 1 addition & 0 deletions jvm/src/main/scala-2/src/main/scala/mouse/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package object mouse extends MouseFunctions {
object feither extends FEitherSyntax
object fnested extends FNestedSyntax
object foption extends FOptionSyntax
object ftuple extends FTupleSyntax
object int extends IntSyntax
object long extends LongSyntax
object map extends MapSyntax
Expand Down
1 change: 1 addition & 0 deletions jvm/src/main/scala-3/src/main/scala/mouse/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package object mouse extends MouseFunctions {
object feither extends FEitherSyntax
object fnested extends FNestedSyntax
object foption extends FOptionSyntax
object ftuple extends FTupleSyntax
object int extends IntSyntax
object long extends LongSyntax
object map extends MapSyntax
Expand Down
82 changes: 82 additions & 0 deletions project/Boilerplate.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import sbt._

import scala.annotation.tailrec
import scala.collection.immutable

/**
* Copied, with some modifications, from https://github.com/milessabin/shapeless/blob/main/project/Boilerplate.scala
*
* Generate a range of boilerplate classes, those offering alternatives with 0-22 params and would be tedious to craft
* by hand
*
* @author
* Miles Sabin
* @author
* Kevin Wright
*/
object Boilerplate {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that should be a separate library to prevent this copy-pasting among cats-* libraries. But there is none at the moment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. I mean that Typelevel could have its own. Because there is an obvious need.


import scala.StringContext._

implicit final class BlockHelper(private val sc: StringContext) extends AnyVal {
def block(args: Any*): String = {
val interpolated = sc.standardInterpolator(treatEscapes, args)
val rawLines = interpolated.split('\n')
val trimmedLines = rawLines.map(_.dropWhile(_.isWhitespace))
trimmedLines.mkString("\n")
}
}

val templates: Seq[Template] = Seq(GenFTupleNSyntax)

val header: String =
"""// auto-generated boilerplate by /project/Boilerplate.scala
|package mouse
|""".stripMargin

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

val maxArity: Int = 22

final class TemplateVals(val arity: Int) {
val synTypes: immutable.Seq[String] = (0 until arity).map(n => s"A$n")
val `A..N`: String = synTypes.mkString(", ")
}

trait Template {
def filename(root: File): File

def content(tv: TemplateVals): String

def range = 1 to maxArity

def body: String = {
@tailrec
def expandInstances(contents: IndexedSeq[Array[String]], acc: Array[String] = Array.empty): Array[String] =
if (!contents.exists(_.exists(_.startsWith("-"))))
acc.map(_.tail)
else {
val pre = contents.head.takeWhile(_.startsWith("|"))
val instances = contents.flatMap(_.dropWhile(_.startsWith("|")).takeWhile(_.startsWith("-")))
val next = contents.map(_.dropWhile(_.startsWith("|")).dropWhile(_.startsWith("-")))
expandInstances(next, acc ++ pre ++ instances)
}

val rawContents = range.map { n =>
content(new TemplateVals(n)).split('\n').filterNot(_.isEmpty)
}
val headerLines = header.split('\n')
val instances = expandInstances(rawContents)
val footerLines = rawContents.head.reverse.takeWhile(_.startsWith("|")).map(_.tail).reverse
(headerLines ++ instances ++ footerLines).mkString("\n")
}
}
}
42 changes: 42 additions & 0 deletions project/GenFTupleNSyntax.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import sbt._

import Boilerplate._
import Boilerplate.{Template, TemplateVals}
import sbt.File

object GenFTupleNSyntax extends Template {
// we generate syntax for Tuple3..22 because already there is [[cats.syntax.FunctorTuple2Ops]].
override def range = 3 to maxArity
override def filename(root: sbt.File): File =
root / "mouse" / "FTupleNSyntax.scala"

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

val generatedFunctions: String =
(1 to arity)
.map { n =>
s"""
- /**
- * Lifts [[Tuple$arity._$n]] into `F[_]`.
- */
- def _${n}F(implicit F: Functor[F]): F[A${n - 1}] = F.map(ftuple)(_._$n)
-
"""
}
.mkString("\n")

block"""
|
|import cats.Functor
|
|trait FTupleNSyntax {
- implicit final def mouseSyntaxFTuple${arity}Ops[F[_], ${`A..N`}](ftuple: F[(${`A..N`})]): FTuple${arity}Ops[F, ${`A..N`}] = new FTuple${arity}Ops[F, ${`A..N`}](ftuple)
-
- private[mouse] final class FTuple${arity}Ops[F[_], ${`A..N`}](ftuple: F[(${`A..N`})]) extends Serializable {
$generatedFunctions
- }
-
|}"""
}
}
1 change: 1 addition & 0 deletions shared/src/main/scala-2/src/main/scala/mouse/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ trait AllSharedSyntax
with FEitherSyntax
with FNestedSyntax
with FOptionSyntax
with FTupleSyntax
with IntSyntax
with LongSyntax
with MapSyntax
Expand Down
1 change: 1 addition & 0 deletions shared/src/main/scala-3/src/main/scala/mouse/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ trait AllSharedSyntax
with FEitherSyntax
with FNestedSyntax
with FOptionSyntax
with FTupleSyntax
with IntSyntax
with LongSyntax
with MapSyntax
Expand Down
5 changes: 5 additions & 0 deletions shared/src/main/scala/mouse/ftuple.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package mouse

trait FTupleSyntax extends FTupleNSyntax

object FTupleSyntax
Loading