AUXify
Introduces macro/meta annotations @ aux, @ self, @ instance, @ apply, @ delegated, @ syntax and String-based type class LabelledGeneric
Install / Use
/learn @DmytroMitin/AUXifyREADME
AUXify
Contents
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](https://typelevel.org/blog
