AUXify
AUXify copied to clipboard
Introduces macro/meta annotations @ aux, @ self, @ instance, @ apply, @ delegated, @ syntax and String-based type class LabelledGeneric
AUXify
Contents
- Using AUXify-Shapeless
-
Using AUXify-Macros
- @aux (helper for type refinement)
- @self
- @instance (constructor)
- @apply (materializer)
- @delegated
- @syntax
- @poly
-
Using AUXify-Meta
- Code generation with Scalafix
- Rewriting with Scalafix
- Code generation with Scalameta
Using AUXify-Shapeless
Write in build.sbt
scalaVersion := "2.13.3"
//scalaVersion := "2.12.11"
//scalaVersion := "2.11.12"
//scalaVersion := "2.10.7"
resolvers += Resolver.sonatypeRepo("public")
libraryDependencies ++= Seq(
"com.github.dmytromitin" %% "auxify-shapeless" % [LATEST VERSION],
"com.github.dmytromitin" %% "shapeless" % (CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, v)) if v >= 11 => "2.4.0-M1-30032020-e6c3f71-PATCH"
case _ => "2.4.0-SNAPSHOT-18022020-bf55524-PATCH"
})
)
Helps to overcome Shapeless limitation that shapeless.LabelledGeneric
is Symbol
-based rather than String
-based.
Introduces type classes SymbolToString
, StringToSymbol
to convert between symbol singleton type and string singleton type
implicitly[StringToSymbol.Aux["a", Symbol @@ "a"]]
implicitly[SymbolToString.Aux[Symbol @@ "a", "a"]]
stringToSymbol("a") // returns Symbol("a") of type Symbol @@ "a"
symbolToString(Symbol("a")) // returns "a" of type "a"
and String
-based type class com.github.dmytromitin.auxify.shapeless.LabelledGeneric
case class A(i: Int, s: String, b: Boolean)
implicitly[LabelledGeneric.Aux[A, Record.`"i" -> Int, "s" -> String, "b" -> Boolean`.T]]
LabelledGeneric[A].to(A(1, "a", true)) // field["i"](1) :: field["s"]("a") :: field["b"](true) :: HNil
LabelledGeneric[A].from(field["i"](1) :: field["s"]("a") :: field["b"](true) :: HNil) // A(1, "a", true)
Also there are convenient syntaxes
import com.github.dmytromitin.auxify.shapeless.hlist._
import StringsToSymbols.syntax._
("a".narrow :: "b".narrow :: "c".narrow :: HNil).stringsToSymbols // 'a.narrow :: 'b.narrow :: 'c.narrow :: HNil
import SymbolsToStrings.syntax._
('a.narrow :: 'b.narrow :: 'c.narrow :: HNil).symbolsToStrings // "a".narrow :: "b".narrow :: "c".narrow :: HNil
import com.github.dmytromitin.auxify.shapeless.coproduct._
import StringsToSymbols.syntax._
(Inr(Inr(Inl("c".narrow))) : "a" :+: "b" :+: "c" :+: CNil).stringsToSymbols // Inr(Inr(Inl('c.narrow))) : (Symbol @@ "a") :+: (Symbol @@ "b") :+: (Symbol @@ "c") :+: CNil
import SymbolsToStrings.syntax._
(Inr(Inr(Inl('c.narrow))) : (Symbol @@ "a") :+: (Symbol @@ "b") :+: (Symbol @@ "c") :+: CNil).symbolsToStrings // Inr(Inr(Inl("c".narrow))) : "a" :+: "b" :+: "c" :+: CNil
import com.github.dmytromitin.auxify.shapeless.record._
import StringsToSymbols.syntax._
(field["a"](1) :: field["b"]("s") :: field["c"](true) :: HNil).stringsToSymbols // field[Symbol @@ "a"](1) :: field[Symbol @@ "b"]("s") :: field[Symbol @@ "c"](true) :: HNil
import SymbolsToStrings.syntax._
(field[Symbol @@ "a"](1) :: field[Symbol @@ "b"]("s") :: field[Symbol @@ "c"](true) :: HNil).symbolsToStrings // field["a"](1) :: field["b"]("s") :: field["c"](true) :: HNil
import com.github.dmytromitin.auxify.shapeless.union._
import StringsToSymbols.syntax._
(Inr(Inr(Inl(field["c"](true)))): Union.`"a" -> Int, "b" -> String, "c" -> Boolean`.T).stringsToSymbols // Inr(Inr(Inl(field[Witness.`'c`.T](true)))): Union.`'a -> Int, 'b -> String, 'c -> Boolean`.T
import SymbolsToStrings.syntax._
(Inr(Inr(Inl(field[Symbol @@ "c"](true)))): Union.`'a -> Int, 'b -> String, 'c -> Boolean`.T).symbolsToStrings // Inr(Inr(Inl(field[Witness.`"c"`.T](true)))): Union.`"a" -> Int, "b" -> String, "c" -> Boolean`.T
You can play with AUXify online at Scastie: https://scastie.scala-lang.org/r52fCgloRc2VVM5FnNmbsQ
Using AUXify-Macros
Write in build.sbt
scalaVersion := "2.13.3"
//scalaVersion := "2.12.11"
//scalaVersion := "2.11.12"
//scalaVersion := "2.10.7"
resolvers += Resolver.sonatypeRepo("public")
libraryDependencies += "com.github.dmytromitin" %% "auxify-macros" % [LATEST VERSION]
scalacOptions += "-Ymacro-annotations" // in Scala >= 2.13
//addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) // in Scala <= 2.12
@aux (helper for type refinement)
Transforms
@aux
trait Add[N <: Nat, M <: Nat] {
type Out <: Nat
def apply(n: N, m: M): Out
}
into
trait Add[N <: Nat, M <: Nat] {
type Out <: Nat
def apply(n: N, m: M): Out
}
object Add {
type Aux[N <: Nat, M <: Nat, Out0 <: Nat] = Add[N, M] { type Out = Out0 }
}
So it can be used:
implicitly[Add.Aux[_2, _3, _5]]
Convenient for type-level programming.
@self
Transforms
@self
sealed trait Nat {
type ++ = Succ[Self]
}
@self
case object _0 extends Nat
type _0 = _0.type
@self
case class Succ[N <: Nat](n: N) extends Nat
into
sealed trait Nat { self =>
type Self >: self.type <: Nat { type Self = self.Self }
type ++ = Succ[Self]
}
case object _0 extends Nat {
override type Self = _0
}
type _0 = _0.type
case class Succ[N <: Nat](n: N) extends Nat {
override type Self = Succ[N]
}
Convenient for type-level programming.
Generating lower bound >: self.type
and/or F-bound type Self = self.Self
for trait can be switched off
@self(lowerBound = false, fBound = false)
@instance (constructor)
Transforms
@instance
trait Monoid[A] {
def empty: A
def combine(a: A, a1: A): A
}
into
trait Monoid[A] {
def empty: A
def combine(a: A, a1: A): A
}
object Monoid {
def instance[A](f: => A, f1: (A, A) => A): Monoid[A] = new Monoid[A] {
override def empty: A = f
override def combine(a: A, a1: A): A = f1(a, a1)
}
}
So it can be used
implicit val intMonoid: Monoid[Int] = instance(0, _ + _)
Polymorphic methods are not supported (since Scala 2 lacks polymorphic functions).
@apply (materializer)
Transforms
@apply
trait Show[A] {
def show(a: A): String
}
into
trait Show[A] {
def show(a: A): String
}
object Show {
def apply[A](implicit inst: Show[A]): Show[A] = inst
}
So it can be used
Show[Int].show(10)
Method materializing type class can return more precise type than the one of implicit to be found (like the
in Shapeless or summon
in Dotty).
For example
@apply
trait Add[N <: Nat, M <: Nat] {
type Out <: Nat
def apply(n: N, m: M): Out
}
is transformed into
trait Add[N <: Nat, M <: Nat] {
type Out <: Nat
def apply(n: N, m: M): Out
}
object Add {
def apply[N <: Nat, M <: Nat](implicit inst: Add[N, M]): Add[N, M] { type Out = inst.Out } = inst
}
Simulacrum annotation @typeclass
also generates, among other, materializer but doesn't support type classes with multiple type parameters.
@delegated
Generates methods in companion object delegating to implicit instance of trait (type class).
Transforms
@delegated
trait Show[A] {
def show(a: A): String
}
into
trait Show[A] {
def show(a: A): String
}
object Show {
def show[A](a: A)(implicit inst: Show[A]): String = inst.show(a)
}
So it can be used
Show.show(10)
@syntax
Transforms
@syntax
trait Monoid[A] {
def empty: A
def combine(a: A, a1: A): A
}
into
trait Monoid[A] {
def empty: A
def combine(a: A, a1: A): A
}
object Monoid {
object syntax {
implicit class Ops[A](a: A) {
def combine(a1: A)(implicit inst: Monoid[A]): A = inst.combine(a, a1)
}
}
}
So it can be used
import Monoid.syntax._
2 combine 3
Simulacrum annotation @typeclass
also generates syntax but doesn't support type classes with multiple type parameters.
Inheritance of type classes is not supported (anyway it's broken).
@poly
Transforms
@poly
trait Add[N <: Nat, M <: Nat] {
type Out <: Nat
def apply(n: N, m: M): Out
}
into
trait Add[N <: Nat, M <: Nat] {
type Out <: Nat
def apply(n: N, m: M): Out
}
object Add {
object addPoly extends Poly2 {
implicit def cse[N <: Nat, M <: Nat](implicit add: Add[N, M]): Case.Aux[N, M, add.Out] = at((n, m) => add(n, m))
}
}
@poly
is not implemented yet. See issue.
Using AUXify-Meta
Currently only @aux
is implemented as Scalafix rewriting rule. It's a semantic rule since we need companion object.
Meta annotation @aux
works only with classes on contrary to macro annotation @aux
working only with traits.
This will be fixed.
Code generation with Scalafix
For code generation with Scalameta + SemanticDB + Scalafix write in project/plugins.sbt
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.18")
and in build.sbt
import com.geirsson.coursiersmall.{Repository => R}
lazy val V = _root_.scalafix.sbt.BuildInfo
inThisBuild(Seq(
scalaVersion := V.scala213,
addCompilerPlugin(scalafixSemanticdb),
scalafixResolvers in ThisBuild += new R.Maven("https://oss.sonatype.org/content/groups/public/"),
// brings rewriting rules
scalafixDependencies in ThisBuild += "com.github.dmytromitin" %% "auxify-meta" % [LATEST VERSION],
scalacOptions += "-Yrangepos" // for SemanticDB
))
lazy val in = project
.settings(
// brings meta annotations
libraryDependencies += "com.github.dmytromitin" %% "auxify-meta-core" % [LATEST VERSION]
)
lazy val out = project
.settings(
sourceGenerators.in(Compile) += Def.taskDyn {
val root = baseDirectory.in(ThisBuild).value.toURI.toString
val from = sourceDirectory.in(in, Compile).value
val to = sourceManaged.in(Compile).value
val outFrom = from.toURI.toString.stripSuffix("/").stripPrefix(root)
val outTo = to.toURI.toString.stripSuffix("/").stripPrefix(root)
Def.task {
scalafix
.in(in, Compile)
.toTask(s" AuxRule --out-from=$outFrom --out-to=$outTo")
.value
(to ** "*.scala").get
}
}.taskValue,
// for import statement and if meta annotation is not expanded
libraryDependencies += "com.github.dmytromitin" %% "auxify-meta-core" % [LATEST VERSION]
)
Annotated code should be placed in in/src/main/scala
. Code generation in out/target/scala-2.13/src_managed/main/scala
can be run with sbt out/compile
.
Example project is here.
Rewriting with Scalafix
For using rewriting rules with Scalameta + SemanticDB + Scalafix write in project/plugins.sbt
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.18")
and in build.sbt
// on the top
import com.geirsson.coursiersmall.{Repository => R}
scalafixResolvers in ThisBuild += new R.Maven("https://oss.sonatype.org/content/groups/public/")
scalafixDependencies in ThisBuild += "com.github.dmytromitin" %% "auxify-meta" % [LATEST VERSION]
scalaVersion := "2.13.3"
//scalaVersion := "2.12.11"
//scalaVersion := "2.11.12"
libraryDependencies += "com.github.dmytromitin" %% "auxify-meta-core" % [LATEST VERSION]
addCompilerPlugin(scalafixSemanticdb)
scalacOptions += "-Yrangepos" // for SemanticDB
Rewriting can be run with sbt "scalafix AuxRule"
(details are here).
Code generation with Scalameta
For code generating syntacticly with pure Scalameta (without SemanticDB and Scalafix) write in project/build.sbt
resolvers += Resolver.sonatypeRepo("public")
libraryDependencies += "com.github.dmytromitin" %% "auxify-syntactic-meta" % [LATEST VERSION]
and in build.sbt
inThisBuild(Seq(
scalaVersion := "2.13.3"
//scalaVersion := "2.12.11"
//scalaVersion := "2.11.12"
))
lazy val in = project
.settings(
libraryDependencies += "com.github.dmytromitin" %% "auxify-meta-core" % [LATEST VERSION]
)
lazy val out = project
.settings(
sourceGenerators in Compile += Def.task {
import com.github.dmytromitin.auxify.meta.syntactic.ScalametaTransformer
val finder: PathFinder = sourceDirectory.in(in, Compile).value ** "*.scala"
for(inputFile <- finder.get) yield {
val inputStr = IO.read(inputFile)
val outputFile = sourceManaged.in(Compile).value / inputFile.name
val outputStr = ScalametaTransformer.transform(inputStr)
IO.write(outputFile, outputStr)
outputFile
}
}.taskValue,
// for import statement and if meta annotation is not expanded
libraryDependencies += "com.github.dmytromitin" %% "auxify-meta-core" % [LATEST VERSION]
)
Annotated code should be placed in in/src/main/scala
. Code generation in out/target/scala-2.13/src_managed/main
can be run with sbt out/compile
.
Example project is here.