Unboxed (multi-nested-)tagged + unboxed newtypes. Better and much friendlier alternative to AnyVals.
Tagged & Newtypes - the better and much friendlier alternative to AnyVals.
(multi-nested-)-tagging (compile time subtyping for any class including primitives)
with
for traits, but for tagged types)extends
for classes, but for tagged types, heavily used in Scala Superquants https://github.com/rudogma/scala-superquants )Newtypes
Zero-dependency
Micro size
Intellij Idea compatible 100% (red marks
free)
Scala:
libraryDependencies += "org.rudogma" %% "supertagged" % "2.0-RC2"
ScalaJS
libraryDependencies += "org.rudogma" %%% "supertagged" % "2.0-RC2"
For those who want to check bytecode, have a look at
object Step extends TaggedType[Raw]{
//...implicit scope for Step ...
//...put here all implicits you need and they will be found here without additional imports...
//...if you want to add more operations to Step, just define one more implicit class with ops...
implicit final class Ops(private val value:Type) extends AnyVal {
//... your methods here ...
}
}
type Step = Step.Type
Step <: Raw
. This subtype exists only at compile time.
Subtyping allow you to call directly all methods of Raw, and put tagged value wherever basic Raw needed without additional conversionsWidth
&& Height
)object WidthT extends TaggedTypeT {
type Raw[T] = T
}
type WidthT[T] = WidthT.Type[T]
val v1:WidthT[Int] = WidthT[Int](5) // WidthT.apply[Int](5)
val v2:WidthT[Long] = WidthT[Long](5L)
TaggedTypeT
v1:Int
&& v2:Long
object Counters extends TaggedType[T]{
type Raw[T] = Array[T]
}
type Counters[T] = Counters.Type[T]
val v1:Counters[Long] = Counters[Long](Array(5L))
val v2:Counters[String] = Counters[String](Array("String"))
v1:Array[long]
(array of primitives) && v2:Array[String]
val arr:Array[Array[Array[Array[Int]]]]
val v1 = Width(arr) // searches for first Int and replaces it -> Array[Array[Array[Array[Width]]]]
val v2 = WidthT[Int](arr) // searches for first Int and replaces it -> Array[Array[Array[Array[WidthT[Int]]]]]
val v3 = Counters[Int](arr) // searches for first Array[Int] and replaces it -> Array[Array[Array[Counters]]]
import supertagged.postfix._
value @@ Width
value @@@ Width
value !@@ Width
value untag Width
import supertagged.postfix._
object Step extends NewType[Raw]{
//...implicit scope for Step ...
//...put here all implicits you need and they will be found here without additional imports...
//...if you want to add more operations to Step, just define one more implicit class with ops...
implicit final class Ops(private val value:Type) extends AnyVal {
//... your methods here ...
}
implicit def someCommonImplicit = ... // conversions, wrappers, typeclasess & etc...
}
type Step = Step.Type
Step.raw(newtyped)
(You can still define your own method for newtype via implicit class to call it like this newtypedValue.raw
- or any other name as you wish)implicit class
to resolve methods and generate appropriate code for it (ex: new YouImplicitClass(newtypedValue).yourMethodName()
(or more efficient if you use extends AnyVal
- according to the documentation of Scala)).object WidthT extends NewTypeT {
type Raw[T] = T
}
type WidthT[T] = WidthT.Type[T]
val v1:WidthT[Int] = WidthT[Int](5)
val v2:WidthT[Long] = WidthT[Long](5L)
NewTypeT
v1:java.lang.Integer
&& v2:java.lang.Long
object Counters extends TaggedType[T]{
type Raw[T] = List[T]
}
type Counters[T] = Counters.Type[T]
val v1:Counters[Long] = Counters[Long](List(5L)) // You can't make newtype from Array[primitives], because it will fail at runtime with `can't cast` error
val v2:Counters[String] = Counters[String](List("String"))
v1:List[java.lang.Long]
(array of primitives) && v2:List[String]
Array[T <: AnyRef]
val arr:List[List[List[List[Int]]]]
val v1 = Width(arr) // -> Array[Array[Array[Array[Width]]]]
val v2 = Width[Int](arr) // -> Array[Array[Array[Array[Width[Int]]]]]
val v3 = Counters[Int](arr) // -> Array[Array[Array[Counters]]]
object Unfold extends NewType0 {
protected type T[A,B] = A => Option[(A, B)]
type Type[A,B] = Newtype[T[A,B],Ops[A,B]]
implicit final class Ops[A,B](private val f: Type[A,B]) extends AnyVal {
def apply(x: A): Stream[B] = raw(f)(x) match {
case Some((y, e)) => e #:: apply(y)
case None => Stream.empty
}
}
def apply[A,B](f: T[A,B]):Type[A,B] = tag(f) // `tag` built in helper
def raw[A,B](f:Type[A,B]):T[A,B] = cotag(f) // `cotag` built in helper
}
type Unfold[A,B] = Unfold.Type[A,B]
def digits(base:Int) = Unfold[Int,Int]{
case 0 => None
case x => Some((x / base, x % base))
}
digits(10)(123456).force.toString shouldEqual "Stream(6, 5, 4, 3, 2, 1)"
NewType0
as base for your custom semantics and complex typesAt: newtypes tests
object Meters extends TaggedType0[Long] {
def apply(value:Long):Type = if(value >= 0) TaggedOps(this)(value) else throw new Exception("Can't be less then ZERO")
def option(value:Long):Option[Type] = if(value >= 0) Some( TaggedOps(this)(value)) else None
}
type Meters = Meters.Type
Meters(-1) // will throw Exception
Meters(5) // would be `5:Meters`
Meters.option(-1) // would be `None`
Meters.option(0) // would be `Some(0:Meters)`
TaggedType0
& NewType0
as base for defining your subtypes & newtypes with refined
semantics (in any way you want)You have several options:
import supertagged.@@
import supertagged.lift.LiftF
import io.circe.Encoder
implicit def lift_circeEncoder[T,U](implicit F:Encoder[T]):Encoder[T @@ U] = LiftF[Encoder].lift
implicit def lift_circeDecoder[T,U](implicit F:Decoder[T]):Decoder[T @@ U] = LiftF[Decoder].lift
import supertagged.lift.LiftF
implicit val step_circeEncoder = LiftF[io.circe.Encoder].lift[Step.Raw, Step.Tag]
// -or-
implicit val step_circeEncoder:io.circe.Encoder[Step] = LiftF[io.circe.Encoder].lift
trait LiftedCirce {
type Raw
type Type
implicit def ordering(implicit origin:Ordering[Raw]):Ordering[Type] = unsafeCast(origin)
}
trait LiftedCirceT {
type Raw[T]
type Type[T]
implicit def circeEncoder[T](implicit origin:io.circe.Encoder[Raw[T]]):io.circe.Encoder[Type[T]] = unsafeCast(origin)
implicit def circeDecoder[T](implicit origin:io.circe.Decoder[Raw[T]]):io.circe.Decoder[Type[T]] = unsafeCast(origin)
}
object Step extends TaggedType[Int] with LiftedCirce
type Step = Step.Type
object Step extends TaggedType[Int] {
// (they will be used without additional imports)
implicit def circeEncoder:io.circe.Encoder[Type] = lift
implicit def circeDecoder:io.circe.Decoder[Type] = lift
}
type Step = Step.Type
Or in place:
object Step extends TaggedType[Int]
type Step = Step.Type
//somewhere else...
{
import Step
val liftedEncoder:io.circe.Encoder[Step] = Step.lift
val liftedEncoder = Step.lift[io.circe.Encoder] // will be io.circe.Encoder[Step]
}
Will try auto lift of any requested typeclass Not recommended. Because of loosing control of what you are lifting.
import supertagged.lift.liftAnyF
callMethodWithImplicitTypeClass(step)
val width = Width(5)
width match {
case Width(5) => //...
}
TaggedType[Raw]
&& NewType[Raw]
val widthInt = WidthT[Int](5)
val EInt = WidthT.extractor[Int] // boiler plate, because Scala `match` don't support syntax with type parameters at now. Ex: `case EInt.extractor[Int](1)`
widthInt match {
case EInt(1) => false
case EInt(5) => true
}
TaggedTypeT
&& NewTypeT
with some boilerplatesupertagged.classic
supertagged.postfix
import supertagged.postfix._
if you really need it@@
- double @ used to multi tag@@@
- use explicit triple @ to add multi tagimplicit def ordering
removed from TaggedType
with LiftedOrdering
&& with LiftedOrderingT
if you want to use lifted orderings for your modelsupertagged.lift
(now occasionally import supertagged._
will bring only one implicit @newtypeOps
to scope)Lifting typeclasses
polymorphic expression cannot be instantiated
gone away (because of: can't reproduce after changing internals in supertagged)In versions before 2.0
was scalac specific bug in some very specific cases. Now it is absent.
+
for NewtypesPredef.scala
. But it is not obvious that there is a weird implicit final class any2stringadd { def +(other: String)... }
. Because of autoimport it used by compiler as direct scope for searching implicits. It means, that companion objects are not checked if he has found appropriate implicit in direct scope. So, he will use this any2stringadd.+
if you write x:MyNewType) + argument
import Predef.{any2stringadd => _,_}
- Shadowingimport supertagged.newtypeOps
- will force compiler to check companion object for newtype and prefer implicit ops from it.Original idea by Miles Sabin. Similar implementations are also available in shapeless and scalaz.
import supertagged.classic.@@
sealed trait Width
val value = @@[Width](5) // value is `Int @@ Width`