An alternative to case class and extensions for sealed trait giving much more control over the internal representation of ADTs for Functional Programming in Scala style.
This project is twinned with stalactite (which provides performance optimisations and cleaner syntax for typeclass derivation).
This project was sponsored by the Scala Google Summer of Code. A blog post was written at the end of the programme by Vovapolu with a summary of the initial achievements.
addCompilerPlugin("org.scalameta" % "paradise" % "3.0.0-M10" cross CrossVersion.full)
libraryDependencies += "com.fommil" %% "stalagmite" % "<version>"addCompilerPlugin("org.scalameta" % "paradise" % "3.0.0-M10" cross CrossVersion.full)
resolvers += Resolver.sonatypeRepo("snapshots")
libraryDependencies += "com.fommil" %% "stalagmite" % "<version + 0.0.1>-SNAPSHOT"Stalagmite is built using the next-generation Scala macro system scala.meta.
Unfortunately scala.meta is not yet supported in IntelliJ, Scala IDE, ENSIME, Scaladocs nor the REPL. We await the acceptance of the Scala Center proposal Production Ready Scala Macros.
The following features are currently being considered in src/test/scala/testing. If you have any further ideas, please comment on the issue tracker:
@data(product = true, checkSerializable = false /*, companionExtends = true */)
class Foo[+T](a: Boolean, s: String, t: T, i: Int = 0)should give feature parity with
final case class Foo[+T] private (a: Boolean, s: String, t: T, i: Int = 0)Extending trait / interface is allowed, but extending class / abstract class is forbidden.
User-defined methods and fields are being debated in #5.
product(i.e. implementingProduct) will be disabled by default because it encourages runtime inspection instead of compiletime safety.checkSerializable(i.e. checking all parameters forSerializable) will be enabled by default because this is the sort of thing that should be checked at compiletime.companionExtendsdisabled by default and not possible for non-parametric classes, makes the companion extend a function. Has binary compatibility consequences.
Implicit instances of shapeless.{Generic, LabelledGeneric, Typeable} are generated on the companion. This saves shapeless from having to derive one at every call site, speeding up downstream compiles.
Memoisation uses best endeavours to re-use existing instances instead of creating new ones.
High levels of memoisation in a JVM mean that the overall heap size for a class can be dramatically reduced in some cases, but at the cost of extra CPU cycles during construction and GC pressure. Also, the incidence of instance-based equality hits go up (so equals can get faster!).
@data(memoise = true, memoiseRefs = Seq('s), memoiseHashCode = true, memoiseToString = true, memoiseStrong = true)
class Foo(a: Boolean, s: String)The following features are independent, but can be combined:
memoiseuses an interner cache to reduce duplication on the heap (at the cost of lookup and GC pressure)memoiseRefs(takesSeq[Symbol]) uses a memoisation cache for the selectedAnyReffields (at the cost of lookup and GC pressure)memoizeStringsInternfalse by default, special-casesStringfields to use the JVM'sintern(trumpsmemoiseRefs)memoiseHashCodestores thehashCodein aval, and uses this as a shortcut inequals(at the cost of initialisation and heap)memoiseToStringstores thetoStringin aval(at the cost of initialisation and heap)memoiseStrongweak by default, means your instances are never garbage collected andequalsis identity based (extreme caution!)
Further ideas for memoisation should go in #6
See #8 for a way of speeding up equals for instances with value equality (but not reference equality), at the cost of even more heap.
We are using Guava's {Weak,String}Interner to implement memoisation, but what we want a SoftReference interner that uses a custom Equality.
@data(optimiseHeapOptions = true, optimiseHeapStrings = true)
class Foo(a: Option[Boolean], b: Option[Boolean], s: Option[String])optimiseHeapOptionsstoresOptionAnyRefs asnull-able fields and a (shared) bitmask forAnyVals. Does not allownullorSome(null).optimiseHeapBooleansre-uses the (shared) bitmask for bit packing ofBooleanparametersoptimiseHeapStringsunwrapsStringasArray[Char], saving 64 bits perStringfield per instance (at the cost of object churn andString.{hashCode,equals}performance)
For this example: 3 Option wrappers, Boolean boxing, Boolean packing, String wrapping, we save 6 references (384 bits) per instance.
Note that changing the heap representation does not affect the serialised form (the public visible fields are used).
Further ideas for heap optimisation should go in #3
