Determine non-empty additional fields in a subclass - scala

Assume I have a trait which looks something like this
trait MyTrait {
val x: Option[String] = None
val y: Option[String] = None
}
Post defining the trait I extend this trait to a class MyClass which looks something like this
case class MyClass(
override val x: Option[String] = None,
override val y: Option[String] = None,
z: Option[String] = None
) extends MyTrait
Now I need to find if any other property other than the properties extended by MyTrait is not None. In the sense if I need to write a method which is called getClassInfo which returns true/false based upon the values present in the case class. In this case it should return true if z is Non optional. My getClassInfo goes something like this
def getClassInfo(myClass: MyClass): Boolean = {
myClass
.productIterator
.filterNot(x => x.isInstanceOf[MyTrait])
.exists(_.isInstanceOf[Some[_]])
}
Ideally this should filter out all the fields which are not a part of Mytrait and return me z in this case.
I tried using variance, However It seems like isInstanceOf doesn't take the same
filterNot(x => x.isInstanceOf[+MyTrait])
However this cannot be possible
val a = getClassInfo(MyClass()) //Needs to return false
val b = getClassInfo(MyClass(Some("a"), Some("B"), Some("c"))) //returns true
val c = getClassInfo(MyClass(z = Some("z"))) //needs to return true
val d = getClassInfo(MyClass(x = Some("x"), y = Some("y"))) // needs to return false

The simple answer is to declare an abstract method that gives the result you want and override it in the subclass:
trait MyTrait {
def x: Option[String]
def y: Option[String]
def anyNonEmpty: Boolean = false
}
case class MyClass(x: Option[String] = None, y: Option[String] = None, z: Option[String] = None) extends MyTrait {
override def anyNonEmpty = z.nonEmpty
}
You can then call anyNonEmpty on your object to get the getClassInfo result.
Also note that I've used def here in the trait because val in a trait is generally a bad idea because of initialisation issues.

If you really need reflection you can try
import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.universe._
def getClassInfo(myClass: MyClass): Boolean = {
def fields[A: TypeTag] = typeOf[A].members.collect {
case m: MethodSymbol if m.isGetter && m.isPublic => m
}
val mtFields = fields[MyTrait]
val mcFields = fields[MyClass]
val mtFieldNames = mtFields.map(_.name).toSet
val mcNotMtFields = mcFields.filterNot(f => mtFieldNames.contains(f.name))
val instanceMirror = currentMirror.reflect(myClass)
val mcNotMtFieldValues = mcNotMtFields.map(f => instanceMirror.reflectField(f).get)
mcNotMtFieldValues.exists(_.isInstanceOf[Some[_]])
}
val a = getClassInfo(MyClass()) //false
val b = getClassInfo(MyClass(Some("a"), Some("B"), Some("c"))) //true
val c = getClassInfo(MyClass(z = Some("z"))) //true
val d = getClassInfo(MyClass(x = Some("x"), y = Some("y")))//false

Related

Is it possible to write a upickle Serializer for akka

I would like to implement an akka Serializer using upickle but I'm not sure its possible. To do so I would need to implement a Serializer something like the following:
import akka.serialization.Serializer
import upickle.default._
class UpickleSerializer extends Serializer {
def includeManifest: Boolean = true
def identifier = 1234567
def toBinary(obj: AnyRef): Array[Byte] = {
writeBinary(obj) // ???
}
def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = {
readBinary(bytes) // ???
}
}
The problem is I cannot call writeBinary/readBinary without having the relevant Writer/Reader. Is there a way I can look these up based on the object class?
Take a look at following files, you should get some ideas!
CborAkkaSerializer.scala
LocationAkkaSerializer.scala
Note: These serializers are using cbor
I found a way to do it using reflection. I base the solution on the assumption that any object that needs to be serialized should have defined a ReadWriter in its companion object:
class UpickleSerializer extends Serializer {
private var map = Map[Class[_], ReadWriter[AnyRef]]()
def includeManifest: Boolean = true
def identifier = 1234567
def toBinary(obj: AnyRef): Array[Byte] = {
implicit val rw = getReadWriter(obj.getClass)
writeBinary(obj)
}
def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = {
implicit val rw = lookup(clazz.get)
readBinary[AnyRef](bytes)
}
private def getReadWriter(clazz: Class[_]) = map.get(clazz) match {
case Some(rw) => rw
case None =>
val rw = lookup(clazz)
map += clazz -> rw
rw
}
private def lookup(clazz: Class[_]) = {
import scala.reflect.runtime._
val rootMirror = universe.runtimeMirror(clazz.getClassLoader)
val classSymbol = rootMirror.classSymbol(clazz)
val moduleSymbol = classSymbol.companion.asModule
val moduleMirror = rootMirror.reflectModule(moduleSymbol)
val instanceMirror = rootMirror.reflect(moduleMirror.instance)
val members = instanceMirror.symbol.typeSignature.members
members.find(_.typeSignature <:< typeOf[ReadWriter[_]]) match {
case Some(rw) =>
instanceMirror.reflectField(rw.asTerm).get.asInstanceOf[ReadWriter[AnyRef]]
case None =>
throw new RuntimeException("Not found")
}
}
}

Proper way to guard function operations using Option[] arguments

I have code where a class can provide modified copies of itself, like so:
case class A(i: Int, s: String) {
def foo(ii: Int): A = copy(i = ii)
def bar(ss: String): A = copy(s = ss)
}
I want to create a function that takes some optional arguments and creates these modified copies using these arguments if they are defined:
def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
if (oi.isDefined && os.isDefined)
a.foo(oi.get).bar(os.get)
else if (oi.isDefined && !os.isDefined)
a.foo(oi.get)
else if (!oi.isDefined && os.isDefined)
a.bar(os.get)
else
a
}
This is clearly not sustainable, as I add new optional arguments, I have to create cases for every combination of arguments...
I also cannot do:
a.foo(oi.getOrElse(a.i)).bar(os.getOrElse(a.s))
Because in my actual code, if oi or os is not provided, I should NOT run their associated foo and bar functions. In other words, I have no default arguments for oi and os, rather their existence defines whether I should run certain functions at all.
Current solution, extend the class:
implicit class A_extended(a: A) {
def fooOption(oi: Option[Int]): A = if (oi.isDefined) a.foo(oi.get) else a
def barOption(os: Option[String]): A = if (os.isDefined) a.bar(os.get) else a
}
def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
a.fooOption(oi).barOption(os)
}
But this problem comes up often and it's a bit tedious to do this constantly, is there something like:
// oi: Option[Int], foo: Int => A
oi.ifDefinedThen(a.foo(_), a) // returns a.foo(oi.get) if oi is not None, else just a
Or should I just extend Option to provide this functionality?
Use fold on option final def fold[B](ifEmpty: => B)(f: A => B): B
def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
val oia = oi.fold(a)(a.foo)
os.fold(oia)(oia.bar)
}
Scala REPL
scala> def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
val oia = oi.fold(a)(a.foo)
os.fold(oia)(oia.bar)
}
defined function subA
scala> subA(A(1, "bow"), Some(2), Some("cow"))
res10: A = A(2, "cow")
or
Use pattern matching to deal with options elegantly. Create a tuple of options and then use pattern matching to extract the inner values
val a = Some(1)
val b = Some("some string")
(a, b) match {
case (Some(x), Some(y)) =>
case (Some(x), _) =>
case (_, Some(y)) =>
case (_, _) =>
}
Well... You can use reflection to create arbitrary copiers and even updaters for your case classes.
The difference is that an updater updates the case class instance and the copier create a new copy with updated fields.
An implementation of an updater can be done as below,
import scala.language.existentials
import scala.reflect.runtime.{universe => ru}
def copyInstance[C: scala.reflect.ClassTag](instance: C, mapOfUpdates: Map[String, T forSome {type T}]): C = {
val runtimeMirror = ru.runtimeMirror(instance.getClass.getClassLoader)
val instanceMirror = runtimeMirror.reflect(instance)
val tpe = instanceMirror.symbol.toType
val copyMethod = tpe.decl(ru.TermName("copy")).asMethod
val copyMethodInstance = instanceMirror.reflectMethod(copyMethod)
val updates = tpe.members
.filter(member => member.asTerm.isCaseAccessor && member.asTerm.isMethod)
.map(member => {
val term = member.asTerm
//check if we need to update it or use the instance value
val updatedValue = mapOfUpdates.getOrElse(
key = term.name.toString,
default = instanceMirror.reflectField(term).get
)
updatedValue
}).toSeq.reverse
val copyOfInstance = copyMethodInstance(updates: _*).asInstanceOf[C]
copyOfInstance
}
def updateInstance[C: scala.reflect.ClassTag](instance: C, mapOfUpdates: Map[String, T forSome {type T}]): C = {
val runtimeMirror = ru.runtimeMirror(instance.getClass.getClassLoader)
val instanceMirror = runtimeMirror.reflect(instance)
val tpe = instanceMirror.symbol.toType
tpe.members.foreach(member => {
val term = member.asTerm
term.isCaseAccessor && term.isMethod match {
case true =>
// it is a case class accessor, check if we need to update it
mapOfUpdates.get(term.name.toString).foreach(updatedValue => {
val fieldMirror = instanceMirror.reflectField(term.accessed.asTerm)
// filed mirrors can even update immutable fields !!
fieldMirror.set(updatedValue)
})
case false => // Not a case class accessor, do nothing
}
})
instance
}
And since you wanted to use Options to copy, here is your define once and use with all case classes copyUsingOptions
def copyUsingOptions[C: scala.reflect.ClassTag](instance: C, listOfUpdateOptions: List[Option[T forSome {type T}]]): C = {
val runtimeMirror = ru.runtimeMirror(instance.getClass.getClassLoader)
val instanceMirror = runtimeMirror.reflect(instance)
val tpe = instanceMirror.symbol.toType
val copyMethod = tpe.decl(ru.TermName("copy")).asMethod
val copyMethodInstance = instanceMirror.reflectMethod(copyMethod)
val updates = tpe.members.toSeq
.filter(member => member.asTerm.isCaseAccessor && member.asTerm.isMethod)
.reverse
.zipWithIndex
.map({ case (member, index) =>
listOfUpdateOptions(index).getOrElse(instanceMirror.reflectField(member.asTerm).get)
})
val copyOfInstance = copyMethodInstance(updates: _*).asInstanceOf[C]
copyOfInstance
}
Now you can use these updateInstance or copyInstance to update or copy instances of any case classes,
case class Demo(id: Int, name: String, alliance: Option[String], power: Double, lat: Double, long: Double)
// defined class Demo
val d1 = Demo(1, "player_1", None, 15.5, 78.404, 71.404)
// d1: Demo = Demo(1,player_1,None,15.5,78.404,71.404)
val d1WithAlliance = copyInstance(d1, Map("alliance" -> Some("Empires")))
// d1WithAlliance: Demo = Demo(1,player_1,Some(Empires),15.5,78.404,71.404)
val d2 = copyInstance(d1, Map("id" -> 2, "name" -> "player_2"))
d2: Demo = Demo(2,player_2,None,15.5,78.404,71.404)
val d3 = copyWithOptions(
d1, List(Some(3),
Some("player_3"), Some(Some("Vikings")), None, None, None)
)
// d3: Demo = Demo(3,player_3,Some(Vikings),15.5,78.404,71.404)
// Or you can update instance using updateInstance
val d4 = updateInstance(d1, Map("id" -> 4, "name" -> "player_4"))
// d4: Demo = Demo(4,player_4,None,15.5,78.404,71.404)
d1
// d1: Demo = Demo(4,player_4,None,15.5,78.404,71.404)
Another option (no pun intended, heh) would be to have foo and bar themselves take and fold over Options:
case class A(i: Int, s: String) {
def foo(optI: Option[Int]): A =
optI.fold(this)(ii => copy(i = ii))
def bar(optS: Option[String]): A =
optS.fold(this)(ss => copy(s = ss))
}
Then, subA can be minimal:
object A {
def subA(
a: A,
optI: Option[Int] = None,
optS: Option[String] = None): A =
a foo optI bar optS
}
You can also overload foo and bar to take plain Int and String as well if you have to maintain the API; in that case make the Option-taking methods call out to their corresponding non-Option-taking ones.

Instantiating a case class with default args via reflection

I need to be able to instantiate various case classes through reflection, both by figuring out the argument types of the constructor, as well as invoking the constructor with all default arguments.
I've come as far as this:
import reflect.runtime.{universe => ru}
val m = ru.runtimeMirror(getClass.getClassLoader)
case class Bar(i: Int = 33)
val tpe = ru.typeOf[Bar]
val classBar = tpe.typeSymbol.asClass
val cm = m.reflectClass(classBar)
val ctor = tpe.declaration(ru.nme.CONSTRUCTOR).asMethod
val ctorm = cm.reflectConstructor(ctor)
// figuring out arg types
val arg1 = ctor.paramss.head.head
arg1.typeSignature =:= ru.typeOf[Int] // true
// etc.
// instantiating with given args
val p = ctorm(33)
Now the missing part:
val p2 = ctorm() // IllegalArgumentException: wrong number of arguments
So how can I create p2 with the default arguments of Bar, i.e. what would be Bar() without reflection.
So in the linked question, the :power REPL uses internal API, which means that defaultGetterName is not available, so we need to construct that from hand. An adoption from #som-snytt 's answer:
def newDefault[A](implicit t: reflect.ClassTag[A]): A = {
import reflect.runtime.{universe => ru, currentMirror => cm}
val clazz = cm.classSymbol(t.runtimeClass)
val mod = clazz.companionSymbol.asModule
val im = cm.reflect(cm.reflectModule(mod).instance)
val ts = im.symbol.typeSignature
val mApply = ts.member(ru.newTermName("apply")).asMethod
val syms = mApply.paramss.flatten
val args = syms.zipWithIndex.map { case (p, i) =>
val mDef = ts.member(ru.newTermName(s"apply$$default$$${i+1}")).asMethod
im.reflectMethod(mDef)()
}
im.reflectMethod(mApply)(args: _*).asInstanceOf[A]
}
case class Foo(bar: Int = 33)
val f = newDefault[Foo] // ok
Is this really the shortest path?
Not minimized... and not endorsing...
scala> import scala.reflect.runtime.universe
import scala.reflect.runtime.universe
scala> import scala.reflect.internal.{ Definitions, SymbolTable, StdNames }
import scala.reflect.internal.{Definitions, SymbolTable, StdNames}
scala> val ds = universe.asInstanceOf[Definitions with SymbolTable with StdNames]
ds: scala.reflect.internal.Definitions with scala.reflect.internal.SymbolTable with scala.reflect.internal.StdNames = scala.reflect.runtime.JavaUniverse#52a16a10
scala> val n = ds.newTermName("foo")
n: ds.TermName = foo
scala> ds.nme.defaultGetterName(n,1)
res1: ds.TermName = foo$default$1
Here's a working version that you can copy into your codebase:
import scala.reflect.api
import scala.reflect.api.{TypeCreator, Universe}
import scala.reflect.runtime.universe._
object Maker {
val mirror = runtimeMirror(getClass.getClassLoader)
var makerRunNumber = 1
def apply[T: TypeTag]: T = {
val method = typeOf[T].companion.decl(TermName("apply")).asMethod
val params = method.paramLists.head
val args = params.map { param =>
makerRunNumber += 1
param.info match {
case t if t <:< typeOf[Enumeration#Value] => chooseEnumValue(convert(t).asInstanceOf[TypeTag[_ <: Enumeration]])
case t if t =:= typeOf[Int] => makerRunNumber
case t if t =:= typeOf[Long] => makerRunNumber
case t if t =:= typeOf[Date] => new Date(Time.now.inMillis)
case t if t <:< typeOf[Option[_]] => None
case t if t =:= typeOf[String] && param.name.decodedName.toString.toLowerCase.contains("email") => s"random-$arbitrary#give.asia"
case t if t =:= typeOf[String] => s"arbitrary-$makerRunNumber"
case t if t =:= typeOf[Boolean] => false
case t if t <:< typeOf[Seq[_]] => List.empty
case t if t <:< typeOf[Map[_, _]] => Map.empty
// Add more special cases here.
case t if isCaseClass(t) => apply(convert(t))
case t => throw new Exception(s"Maker doesn't support generating $t")
}
}
val obj = mirror.reflectModule(typeOf[T].typeSymbol.companion.asModule).instance
mirror.reflect(obj).reflectMethod(method)(args:_*).asInstanceOf[T]
}
def chooseEnumValue[E <: Enumeration: TypeTag]: E#Value = {
val parentType = typeOf[E].asInstanceOf[TypeRef].pre
val valuesMethod = parentType.baseType(typeOf[Enumeration].typeSymbol).decl(TermName("values")).asMethod
val obj = mirror.reflectModule(parentType.termSymbol.asModule).instance
mirror.reflect(obj).reflectMethod(valuesMethod)().asInstanceOf[E#ValueSet].head
}
def convert(tpe: Type): TypeTag[_] = {
TypeTag.apply(
runtimeMirror(getClass.getClassLoader),
new TypeCreator {
override def apply[U <: Universe with Singleton](m: api.Mirror[U]) = {
tpe.asInstanceOf[U # Type]
}
}
)
}
def isCaseClass(t: Type) = {
t.companion.decls.exists(_.name.decodedName.toString == "apply") &&
t.decls.exists(_.name.decodedName.toString == "copy")
}
}
And, when you want to use it, you can call:
val user = Maker[User]
val user2 = Maker[User].copy(email = "someemail#email.com")
The code above generates arbitrary and unique values. The data aren't exactly randomised. It's best for using in tests.
It works with Enum and nested case class. You can also easily extend it to support some other special types.
Read our full blog post here: https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html
This is the most complete example how to create case class via reflection with default constructor parameters(Github source):
import scala.reflect.runtime.universe
import scala.reflect.internal.{Definitions, SymbolTable, StdNames}
object Main {
def newInstanceWithDefaultParameters(className: String): Any = {
val runtimeMirror: universe.Mirror = universe.runtimeMirror(getClass.getClassLoader)
val ds = universe.asInstanceOf[Definitions with SymbolTable with StdNames]
val classSymbol = runtimeMirror.staticClass(className)
val classMirror = runtimeMirror.reflectClass(classSymbol)
val moduleSymbol = runtimeMirror.staticModule(className)
val moduleMirror = runtimeMirror.reflectModule(moduleSymbol)
val moduleInstanceMirror = runtimeMirror.reflect(moduleMirror.instance)
val defaultValueMethodSymbols = moduleMirror.symbol.info.members
.filter(_.name.toString.startsWith(ds.nme.defaultGetterName(ds.newTermName("apply"), 1).toString.dropRight(1)))
.toSeq
.reverse
.map(_.asMethod)
val defaultValueMethods = defaultValueMethodSymbols.map(moduleInstanceMirror.reflectMethod).toList
val primaryConstructorMirror = classMirror.reflectConstructor(classSymbol.primaryConstructor.asMethod)
primaryConstructorMirror.apply(defaultValueMethods.map(_.apply()): _*)
}
def main(args: Array[String]): Unit = {
val instance = newInstanceWithDefaultParameters(classOf[Bar].getName)
println(instance)
}
}
case class Bar(i: Int = 33)

Merge two case class of same type, except some fields

If you have a case class like:
case class Foo(x: String, y: String, z: String)
And you have two instances like:
Foo("x1","y1","z1")
Foo("x2","y2","z2")
Is it possible to merge instance 1 in instance 2, except for field z, so that the result would be:
Foo("x1","y1","z2")
My usecase is just that I give JSON objects to a Backbone app through a Scala API, and the Backbone app gives me back a JSON of the same structure so that I can save/update it. These JSON objects are parsed as case class for easy Scala manipulation. But some fields should never be updated by the client side (like creationDate). For now I'm doing a manual merge but I'd like a more generic solution, a bit like an enhanced copy function.
What I'd like is something like this:
instanceFromDB.updateWith(instanceFromBackbone, excludeFields = "creationDate" )
But I'd like it to be typesafe :)
Edit:
My case class have a lot more fields and I'd like the default bevavior to merge fields unless I explicitly say to not merge them.
What you want is already there; you just need to approach the problem the other way.
case class Bar(x: String, y: String)
val b1 = Bar("old", "tired")
val b2 = Bar("new", "fresh")
If you want everything in b2 not specifically mentioned, you should copy from b2; anything from b1 you want to keep you can mention explicitly:
def keepY(b1: Bar, b2: Bar) = b2.copy(y = b1.y)
scala> keepY(b1, b2)
res1: Bar = Bar(new,tired)
As long as you are copying between two instances of the same case class, and the fields are immutable like they are by default, this will do what you want.
case class Foo(x: String, y: String, z: String)
Foo("old_x", "old_y", "old_z")
// res0: Foo = Foo(old_x,old_y,old_z)
Foo("new_x", "new_y", "new_z")
// res1: Foo = Foo(new_x,new_y,new_z)
// use copy() ...
res0.copy(res1.x, res1.y)
// res2: Foo = Foo(new_x,new_y,old_z)
// ... with by-name parameters
res0.copy(y = res1.y)
// res3: Foo = Foo(old_x,new_y,old_z)
You can exclude class params from automatic copying by the copy method by currying:
case class Person(name: String, age: Int)(val create: Long, val id: Int)
This makes it clear which are ordinary value fields which the client sets and which are special fields. You can't accidentally forget to supply a special field.
For the use case of taking the value fields from one instance and the special fields from another, by reflectively invoking copy with either default args or the special members of the original:
import scala.reflect._
import scala.reflect.runtime.{ currentMirror => cm }
import scala.reflect.runtime.universe._
import System.{ currentTimeMillis => now }
case class Person(name: String, age: Int = 18)(val create: Long = now, val id: Int = Person.nextId) {
require(name != null)
require(age >= 18)
}
object Person {
private val ns = new java.util.concurrent.atomic.AtomicInteger
def nextId = ns.getAndIncrement()
}
object Test extends App {
/** Copy of value with non-defaulting args from model. */
implicit class Copier[A: ClassTag : TypeTag](val value: A) {
def copyFrom(model: A): A = {
val valueMirror = cm reflect value
val modelMirror = cm reflect model
val name = "copy"
val copy = (typeOf[A] member TermName(name)).asMethod
// either defarg or default val for type of p
def valueFor(p: Symbol, i: Int): Any = {
val defarg = typeOf[A] member TermName(s"$name$$default$$${i+1}")
if (defarg != NoSymbol) {
println(s"default $defarg")
(valueMirror reflectMethod defarg.asMethod)()
} else {
println(s"def val for $p")
val pmethod = typeOf[A] member p.name
if (pmethod != NoSymbol) (modelMirror reflectMethod pmethod.asMethod)()
else throw new RuntimeException("No $p on model")
}
}
val args = (for (ps <- copy.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
(valueMirror reflectMethod copy)(args: _*).asInstanceOf[A]
}
}
val customer = Person("Bob")()
val updated = Person("Bobby", 37)(id = -1)
val merged = updated.copyFrom(customer)
assert(merged.create == customer.create)
assert(merged.id == customer.id)
}
case class Foo(x: String, y: String, z: String)
val foo1 = Foo("x1", "y1", "z1")
val foo2 = Foo("x2", "y2", "z2")
val mergedFoo = foo1.copy(z = foo2.z) // Foo("x1", "y1", "z2")
If you change Foo later to:
case class Foo(w: String, x: String, y: String, z: String)
No modification will have to be done. Explicitly:
val foo1 = Foo("w1", "x1", "y1", "z1")
val foo2 = Foo("w2", "x2", "y2", "z2")
val mergedFoo = foo1.copy(z = foo2.z) // Foo("w1", "x1", "y1", "z2")

Extending collection classes with extra fields in Scala

I'm looking to create a class that is basically a collection with an extra field. However, I keep running into problems and am wondering what the best way of implementing this is. I've tried to follow the pattern given in the Scala book. E.g.
import scala.collection.IndexedSeqLike
import scala.collection.mutable.Builder
import scala.collection.generic.CanBuildFrom
import scala.collection.mutable.ArrayBuffer
class FieldSequence[FT,ST](val field: FT, seq: IndexedSeq[ST] = Vector())
extends IndexedSeq[ST] with IndexedSeqLike[ST,FieldSequence[FT,ST]] {
def apply(index: Int): ST = return seq(index)
def length = seq.length
override def newBuilder: Builder[ST,FieldSequence[FT,ST]]
= FieldSequence.newBuilder[FT,ST](field)
}
object FieldSequence {
def fromSeq[FT,ST](field: FT)(buf: IndexedSeq[ST])
= new FieldSequence(field, buf)
def newBuilder[FT,ST](field: FT): Builder[ST,FieldSequence[FT,ST]]
= new ArrayBuffer mapResult(fromSeq(field))
implicit def canBuildFrom[FT,ST]:
CanBuildFrom[FieldSequence[FT,ST], ST, FieldSequence[FT,ST]] =
new CanBuildFrom[FieldSequence[FT,ST], ST, FieldSequence[FT,ST]] {
def apply(): Builder[ST,FieldSequence[FT,ST]]
= newBuilder[FT,ST]( _ ) // What goes here?
def apply(from: FieldSequence[FT,ST]): Builder[ST,FieldSequence[FT,ST]]
= from.newBuilder
}
}
The problem is the CanBuildFrom that is implicitly defined needs an apply method with no arguments. But in these circumstances this method is meaningless, as a field (of type FT) is needed to construct a FieldSequence. In fact, it should be impossible to construct a FieldSequence, simply from a sequence of type ST. Is the best I can do to throw an exception here?
Then your class doesn't fulfill the requirements to be a Seq, and methods like flatMap (and hence for-comprehensions) can't work for it.
I'm not sure I agree with Landei about flatMap and map. If you replace with throwing an exception like this, most of the operations should work.
def apply(): Builder[ST,FieldSequence[FT,ST]] = sys.error("unsupported")
From what I can see in TraversableLike, map and flatMap and most other ones use the apply(repr) version. So for comprehensions seemingly work. It also feels like it should follow the Monad laws (the field is just carried accross).
Given the code you have, you can do this:
scala> val fs = FieldSequence.fromSeq("str")(Vector(1,2))
fs: FieldSequence[java.lang.String,Int] = FieldSequence(1, 2)
scala> fs.map(1 + _)
res3: FieldSequence[java.lang.String,Int] = FieldSequence(2, 3)
scala> val fs2 = FieldSequence.fromSeq("str1")(Vector(10,20))
fs2: FieldSequence[java.lang.String,Int] = FieldSequence(10, 20)
scala> for (x <- fs if x > 0; y <- fs2) yield (x + y)
res5: FieldSequence[java.lang.String,Int] = FieldSequence(11, 21, 12, 22)
What doesn't work is the following:
scala> fs.map(_ + "!")
// does not return a FieldSequence
scala> List(1,2).map(1 + _)(collection.breakOut): FieldSequence[String, Int]
java.lang.RuntimeException: unsupported
// this is where the apply() is used
For breakOut to work you would need to implement the apply() method. I suspect you could generate a builder with some default value for field: def apply() = newBuilder[FT, ST](getDefault) with some implementation of getDefault that makes sense for your use case.
For the fact that fs.map(_ + "!") does not preserve the type, you need to modify your signature and implementation, so that the compiler can find a CanBuildFrom[FieldSequence[String, Int], String, FieldSequence[String, String]]
implicit def canBuildFrom[FT,ST_FROM,ST]:
CanBuildFrom[FieldSequence[FT,ST_FROM], ST, FieldSequence[FT,ST]] =
new CanBuildFrom[FieldSequence[FT,ST_FROM], ST, FieldSequence[FT,ST]] {
def apply(): Builder[ST,FieldSequence[FT,ST]]
= sys.error("unsupported")
def apply(from: FieldSequence[FT,ST_FROM]): Builder[ST,FieldSequence[FT,ST]]
= newBuilder[FT, ST](from.field)
}
In the end, my answer was very similar to that in a previous question. The difference with that question and my original and the answer are slight but basically allow anything that has a sequence to be a sequence.
import scala.collection.SeqLike
import scala.collection.mutable.Builder
import scala.collection.mutable.ArrayBuffer
import scala.collection.generic.CanBuildFrom
trait SeqAdapter[+A, Repr[+X] <: SeqAdapter[X,Repr]]
extends Seq[A] with SeqLike[A,Repr[A]] {
val underlyingSeq: Seq[A]
def create[B](seq: Seq[B]): Repr[B]
def apply(index: Int) = underlyingSeq(index)
def length = underlyingSeq.length
def iterator = underlyingSeq.iterator
override protected[this] def newBuilder: Builder[A,Repr[A]] = {
val sac = new SeqAdapterCompanion[Repr] {
def createDefault[B](seq: Seq[B]) = create(seq)
}
sac.newBuilder(create)
}
}
trait SeqAdapterCompanion[Repr[+X] <: SeqAdapter[X,Repr]] {
def createDefault[A](seq: Seq[A]): Repr[A]
def fromSeq[A](creator: (Seq[A]) => Repr[A])(seq: Seq[A]) = creator(seq)
def newBuilder[A](creator: (Seq[A]) => Repr[A]): Builder[A,Repr[A]] =
new ArrayBuffer mapResult fromSeq(creator)
implicit def canBuildFrom[A,B]: CanBuildFrom[Repr[A],B,Repr[B]] =
new CanBuildFrom[Repr[A],B,Repr[B]] {
def apply(): Builder[B,Repr[B]] = newBuilder(createDefault)
def apply(from: Repr[A]) = newBuilder(from.create)
}
}
This fixes all the problems huynhjl brought up. For my original problem, to have a field and a sequence treated as a sequence, a simple class will now do.
trait Field[FT] {
val defaultValue: FT
class FieldSeq[+ST](val field: FT, val underlyingSeq: Seq[ST] = Vector())
extends SeqAdapter[ST,FieldSeq] {
def create[B](seq: Seq[B]) = new FieldSeq[B](field, seq)
}
object FieldSeq extends SeqAdapterCompanion[FieldSeq] {
def createDefault[A](seq: Seq[A]): FieldSeq[A] =
new FieldSeq[A](defaultValue, seq)
override implicit def canBuildFrom[A,B] = super.canBuildFrom[A,B]
}
}
This can be tested as so:
val StringField = new Field[String] { val defaultValue = "Default Value" }
StringField: java.lang.Object with Field[String] = $anon$1#57f5de73
val fs = new StringField.FieldSeq[Int]("str", Vector(1,2))
val fsfield = fs.field
fs: StringField.FieldSeq[Int] = (1, 2)
fsfield: String = str
val fm = fs.map(1 + _)
val fmfield = fm.field
fm: StringField.FieldSeq[Int] = (2, 3)
fmfield: String = str
val fs2 = new StringField.FieldSeq[Int]("str1", Vector(10, 20))
val fs2field = fs2.field
fs2: StringField.FieldSeq[Int] = (10, 20)
fs2field: String = str1
val ffor = for (x <- fs if x > 0; y <- fs2) yield (x + y)
val fforfield = ffor.field
ffor: StringField.FieldSeq[Int] = (11, 21, 12, 22)
fforfield: String = str
val smap = fs.map(_ + "!")
val smapfield = smap.field
smap: StringField.FieldSeq[String] = (1!, 2!)
smapfield: String = str
val break = List(1,2).map(1 + _)(collection.breakOut): StringField.FieldSeq[Int]
val breakfield = break.field
break: StringField.FieldSeq[Int] = (2, 3)
breakfield: String = Default Value
val x: StringField.FieldSeq[Any] = fs
val xfield = x.field
x: StringField.FieldSeq[Any] = (1, 2)
xfield: String = str