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
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
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.
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)
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).
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.
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)
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).
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.
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.
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.
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).
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.