score:2
I know it's quite old, but I bumped at it looking for something else and maybe it'll prove useful. I had a similar motivation, and answered it in how to check I'm inside a specialized function or class
I used a reverse lookup table - SpecializedKey
is a specialized class which equals all other instances with the same specialization, so I can perform a check like this
def onlyBytes[@specialized E](arg :E) :Option[E] =
if (specializationFor[E]==specializationFor[Byte]) Some(arg)
else None
Of course, there's no performance benefit when working with individual primitive values, but with collections, especially iterators, it becomes useful.
final val AllButUnit = new Specializable.Group((Byte, Short, Int, Long, Char, Float, Double, Boolean, AnyRef))
def specializationFor[@specialized(AllButUnit) E] :ResolvedSpecialization[E] =
Specializations(new SpecializedKey[E]).asInstanceOf[ResolvedSpecialization[E]]
private val Specializations = Seq(
resolve[Byte],
resolve[Short],
resolve[Int],
resolve[Long],
resolve[Char],
resolve[Float],
resolve[Double],
resolve[Boolean],
resolve[Unit],
resolve[AnyRef]
).map(
spec => spec.key -> spec :(SpecializedKey[_], ResolvedSpecialization[_])
).toMap.withDefaultValue(resolve[AnyRef])
private def resolve[@specialized(AllButUnit) E :ClassTag] :ResolvedSpecialization[E] =
new ResolvedSpecialization[E](new SpecializedKey[E], new Array[E](0))
class ResolvedSpecialization[@specialized(AllButUnit) E] private[SpecializedCompanion]
(val array :Array[E], val elementType :Class[E], val classTag :ClassTag[E], private[SpecializedCompanion] val key :SpecializedKey[E]) {
private[SpecializedCompanion] def this(key :SpecializedKey[E], array :Array[E]) =
this(array, array.getClass.getComponentType.asInstanceOf[Class[E]], ClassTag(array.getClass.getComponentType.asInstanceOf[Class[E]]), key)
override def toString = s"@specialized($elementType)"
override def equals(that :Any) = that match {
case r :ResolvedSpecialization[_] => r.elementType==elementType
case _ => false
}
override def hashCode = elementType.hashCode
}
private class SpecializedKey[@specialized(AllButUnit) E] {
override def equals(that :Any) = that.getClass==getClass
override def hashCode = getClass.hashCode
def className = getClass.getName
override def toString = className.substring(className.indexOf("$")+1)
}
score:3
This is an answer from the scala internals mailing list:
With miniboxing specialization, you can use the reflection feature:
import MbReflection._
import MbReflection.SimpleType._
import MbReflection.SimpleConv._
object Test {
def bippy[@miniboxed A, @miniboxed B](a: A, b: B): B =
(reifiedType[A], reifiedType[B]) match {
case (`int`, `int`) => (a.as[Int] + b.as[Int]).as[B]
case ( _ , `int`) => (b.as[Int] + 1).as[B]
case (`int`, _ ) => b
case ( _ , _ ) => b
}
def main(args: Array[String]): Unit = {
def x = 1.0
assert(bippy(3,4) == 7)
assert(bippy(x,4) == 5)
assert(bippy(3,x) == x)
assert(bippy(x,x) == x)
}
}
This way, you can choose the exact behavior of the bippy
method based on the type arguments without defining any implicit classes.
score:26
This is my best attempt so far. It works but the implementation isn't pretty (even if the results are). Improvements are welcome!
There is a macro-free way to do this, both at the class and method level, and it does involve type classes--quite a lot of them! And the answer is not exactly the same for classes and methods. So bear with me.
Manually Specialized Classes
You manually specialize classes the same way that you manually provide any kind of different implementation for classes: your superclass is abstract (or is a trait), and the subclasses provide the implementation details.
abstract class Bippy[@specialized(Int) B] {
def b: B
def next: Bippy[B]
}
class BippyInt(initial: Int) extends Bippy[Int] {
private var myB: Int = initial
def b: Int = myB
def next = { myB += 1; this }
}
class BippyObject(initial: Object) extends Bippy[Object] {
private var myB: Object = initial
def b: B = myB
def next = { myB = myB.toString; this }
}
Now, if only we had a specialized method to pick out the right implementations, we'd be done:
object Bippy{
def apply[@specialized(Int) B](initial: B) = ??? // Now what?
}
So we've converted our problem of providing custom specialized classes and methods into just needing to provide custom specialized methods.
Manually Specialized Methods
Manually specializing a method requires a way to write one implementation that can nonetheless select which implementation you want (at compile time). Type classes are great at this. Suppose we already had type classes that implemented all of our functionality, and that the compiler would select the right one. Then we could just write
def foo[@specialized(Int) A: SpecializedFooImpl](a: A): String =
implicitly[SpecializedFooImpl[A]](a)
...or we could if implicitly
was guaranteed to preserve specialization and if we only
ever wanted a single type parameter. In general these things are not true, so we'll write
our type class out as an implicit parameter rather than relying on the A: TC
syntactic sugar.
def foo[@specialized(Int) A](a: A)(implicit impl: SpecializedFooImpl[A]): String =
impl(a)
(Actually, that's less boilerplate anyway.)
So we've converted our problem of providing custom specialized methods into just needing to write specialized typeclasses and getting the compiler to fill in the correct ones.
Manually Specialized Type Classes
Type classes are just classes, and now we have to write specialized classes again, but there's a critical difference. The user isn't the one asking for arbitrary instances. This gives us just enough extra flexibility for it to work.
For foo
, we need an Int
version and a fully generic version.
trait SpecFooImpl[@specialized (Int), A] {
def apply(param: A): String
}
final class SpecFooImplAny[A] extends SpecFooImpl[A] {
def apply(param: A) = param.toString
}
final class SpecFooImplInt extends SpecFooImpl[Int] {
def apply(param: Int) = "!" * math.max(0, param)
}
Now we could create implicits to supply those type classes like so
implicit def specFooAsAny[A] = new SpecFooImplAny[A]
implicit val specFooAsInt = new SpecFooImplInt
except we have a problem: if we actually try to call foo: Int
, both implicits will apply.
So if we just had a way to prioritize which type class we chose, we'd be all set.
Selection of type classes (and implicits in general)
One of the secret ingredients the compiler uses to determine the right implicit to use
is inheritance. If implicits come from A
via B extends A
, but B
declares its own that also could apply, those in B
win if all else is equal.
So we put the ones we want to win deeper in the inheritance hierarchy.
Also, since you're free to define implicits in traits, you can mix them in anywhere.
So the last piece of our puzzle is to pop our type class implicits into a chain of traits that extend each other, with the more generic ones appearing earlier.
trait LowPriorityFooSpecializers {
implicit def specializeFooAsAny[A] = new SpecializedFooImplAny[A]
}
trait FooSpecializers extends LowPriorityFooSpecializers {
implicit val specializeFooAsInt = new SpecializedFooImplInt
}
Mix in the highest-priority trait to wherever the implicits are needed, and the type classes will be picked as desired.
Note that the type classes will be as specialized as you make them even if the
specialized annotation is not used. So you can do without specialized
at all,
as long as you know the type precisely enough, unless you want to use specialized
functions or interoperate with other specialized classes. (And you probably do.)
A complete example
Let's suppose we want to make a two-parameter specialized bippy
function that
will do apply the following transformation:
bippy(a, b) -> b
bippy(a, b: Int) -> b+1
bippy(a: Int, b) -> b
bippy(a: Int, b: Int) -> a+b
We should be able to achieve this with three type classes and a single specialized method. Let's try, first the method:
def bippy[@specialized(Int) A, @specialized(Int) B](a: A, b: B)(implicit impl: SpecBippy[A, B]) =
impl(a, b)
Then the type classes:
trait SpecBippy[@specialized(Int) A, @specialized(Int) B] {
def apply(a: A, b: B): B
}
final class SpecBippyAny[A, B] extends SpecBippy[A, B] {
def apply(a: A, b: B) = b
}
final class SpecBippyAnyInt[A] extends SpecBippy[A, Int] {
def apply(a: A, b: Int) = b + 1
}
final class SpecBippyIntInt extends SpecBippy[Int, Int] {
def apply(a: Int, b: Int) = a + b
}
Then the implicits in chained traits:
trait LowerPriorityBippySpeccer {
// Trick to avoid allocation since generic case is erased anyway!
private val mySpecBippyAny = new SpecBippyAny[AnyRef, AnyRef]
implicit def specBippyAny[A, B] = mySpecBippyAny.asInstanceOf[SpecBippyAny[A, B]]
}
trait LowPriorityBippySpeccer extends LowerPriorityBippySpeccer {
private val mySpecBippyAnyInt = new SpecBippyAnyInt[AnyRef]
implicit def specBippyAnyInt[A] = mySpecBippyAnyInt.asInstanceOf[SpecBippyAnyInt[A]]
}
// Make this last one an object so we can import the contents
object BippySpeccer extends LowPriorityBippySpeccer {
implicit val specBippyIntInt = new SpecBippyIntInt
}
and finally we'll try it out (after pasting everything in together in :paste
in the REPL):
scala> import Speccer._
import Speccer._
scala> bippy(Some(true), "cod")
res0: String = cod
scala> bippy(1, "salmon")
res1: String = salmon
scala> bippy(None, 3)
res2: Int = 4
scala> bippy(4, 5)
res3: Int = 9
It works--our custom implementations are enabled. Just to check that we can use any type, but we don't leak into the wrong implementation:
scala> bippy(4, 5: Short)
res4: Short = 5
scala> bippy(4, 5: Double)
res5: Double = 5.0
scala> bippy(3: Byte, 2)
res6: Int = 3
And finally, to verify that we have actually avoided boxing, we'll time bippy
at
summing a bunch of integers:
scala> val th = new ichi.bench.Thyme
th: ichi.bench.Thyme = ichi.bench.Thyme@1130520d
scala> val adder = (i: Int, j: Int) => i + j
adder: (Int, Int) => Int = <function2>
scala> var a = Array.fill(1024)(util.Random.nextInt)
a: Array[Int] = Array(-698116967, 2090538085, -266092213, ...
scala> th.pbenchOff(){
var i, s = 0
while (i < 1024) { s = adder(a(i), s); i += 1 }
s
}{
var i, s = 0
while (i < 1024) { s = bippy(a(i), s); i += 1 }
s
}
Benchmark comparison (in 1.026 s)
Not significantly different (p ~= 0.2795)
Time ratio: 0.99424 95% CI 0.98375 - 1.00473 (n=30)
First 330.7 ns 95% CI 328.2 ns - 333.1 ns
Second 328.8 ns 95% CI 326.3 ns - 331.2 ns
So we can see that our specialized bippy-adder achieves the same kind of performance as specialized Function2 does (about 3 adds per ns, which is about right for a modern machine).
Summary
To write custom specialized code using the @specialized
annotation,
- Make the specialized class abstract and manually supply concrete implementations
- Make specialized methods (including generators for a specialized class) take typeclasses that do the real work
- Make the base typeclass trait
@specialized
and provide concrete implementations - Provide implicit vals or defs in an inheritance-hierarchy of traits so the correct one is selected
It's a lot of boilerplate, but at the end of it all you get a seamless custom-specialized experience.
Source: stackoverflow.com
Related Query
- How can one provide manually specialized implementations with Scala specialization?
- How can Ordering provide implicit Ordering[T] with type erasure in JVM - Scala
- How can one "associate" test scripts with a Class in Scala
- How can I convert Scala Map to Java Map with scala.Float to java.Float k/v conversion
- How can I use JMH for Scala benchmarks together with sbt?
- How can I "pimp my library" with Scala in a future-proof way?
- How can one list all csv files in an HDFS location within the Spark Scala shell?
- How can I deserialize from JSON with Scala using *non-case* classes?
- How does one use Google Guice's @Inject with Scala / (Play 2.4.x)
- How can I write f(g(h(x))) in Scala with fewer parentheses?
- How can I add scala actors to an existing program without interfering with the normal termination behavior?
- How can you make custom function types in Scala with named parameters?
- How does one compile scala with bigger tuples?
- How can I keep -Xcheckinit from interfering with the deserialization of Scala objects?
- How can I get random data generated for scala case classes with the ability to "change some values" for unit testing?
- In sbt, how can I cross-build with dependencies that are not required in one version?
- How can I generate hash codes with Scala
- Scala - Spark - How to transform a dataframe containing one string column to a DF with columns with the rigth type?
- how can spray.io work with scala 2.11.1 akka 2.3.2
- How can i compile java record with scala code?
- How can I overload functions with generic parameters in scala
- How can I extend Scala collections with an argmax method?
- How can I remove duplicates from a list in Scala with pattern matching?
- How can I provide a scala companion object's class to Java?
- How can I use scala generators to generate date with range?
- I've a table with Map as column data type, how can I explode it to generate 2 columns, one for map and one for key?
- With Scala 2.10.2, SBT 0.13.0, Specs2 & Play Framework 2.2.1 how can I control logging whilst running tests?
- How can you efficiently build one ML model per partition in Spark with foreachPartition?
- How can you sign a POST with OAuth1.0a using scala play?
- How can I validate fields in a table in Vaadin with Scala
More Query from same tag
- How to create instance with type parameter through scala reflection?
- Debug Scala compiler plugin in IntelliJ IDEA
- How to add a Spark Dataframe to the bottom of another dataframe?
- How to get rid of dummy variables when using lazy evaluation in Scala?
- Scala sum of a binary tree tail recursive
- Initialize val on a scalatest fixture
- Pass argument to sbt task
- Is there a way to create custom annotations in Scala and write a custom annotation processor to verify the annotations?
- Best way to insert an element into an ordered array in Scala
- JSON Serializing Scala Case Class to only strings and ints
- Match classes in Scala with Mockito
- error: value show is not a member of Unit CaseFileDFTemp.show()
- Rename assembly-generated uberjar in SBT
- Define a scala function in a play template
- Read Array Of Jsons From File to Spark Dataframe
- Queries with streaming sources must be executed with writeStream.start();
- How to replace values in RDD 1 per keys in RDD 2?
- Automated performance testing framework
- Customise generated Slick SQL for debugging
- Write function with type parameter
- GUI programming with scala: what are the libraries and tools I can use?
- Compile Scala Object using NSC
- What is this Scala construct doing?
- How to sum a few vectors in Scala?
- why the constructor doesn't work with covariance in scala
- SCALA: external command "mail" with "subject"
- Cross product in Scala
- Compare instances of Option[T] avoiding None == None
- spark 2.0 read csv with json
- TransactionalMap vs SynchronizedMap