Nested types in Scala - scala

I have two questions about nested types in Scala.
Imagine I have this kinda trait;
trait ScanList[E] {
sealed trait Command
case object Recover extends Command
case class Remove(item: E) extends Command
sealed trait Event
case class Removed(item: E) extends Event
}
And now I want to write a generic trait over this like this (the problems are encoded in the pattern matches as comment):
trait ScanListProcessor[E] {
type SL = ScanList[E]
def process(msg: SL#Command) = {
msg match {
case u:SL#Remove => // how can instantiate SL#Removed here?
case SL#Recover => //cannot match on nested objects?
}
}
}
The reason for using a trait is that I can derive new implementations of ScanList. In this trait I also have operations like def shouldProcess(item: E): Boolean. For each implementation of ScanList[E] I would like to write generic behaviour like depicted above.
How can pattern match on nested object in a generic type?
Is it possible to instantiate from a type constructor? For example: SL#Removed? I guess it's the same having a generic parameter and trying to construct a value from that, type classes would solve this?

Nested traits, class, and objects in Scala act like inner classes in Java. Any instance of them that you create is associated with an instance of the parent class/trait that they are created from. So continuing from your example:
val sl1 = new ScanList[Int] {}
val sl2 = new ScanList[Int] {}
val r1 = sl1.Removed(1) // has type sl1.Removed
val r2 = sl2.Removed(2) // has type sl2.Removed
val rs = List(r1, r2) // has type List[ScanList[Int]#Removed]
You can pattern match on them like:
rs match {
case List(sl1.Removed(x), sl2.Removed(y)) => (x, y)
}
Pattern matching requires that you explicitly references the parent instance that the nested instance belongs to. Here, we separately match on sl1.Removed and sl2.Removed.
As for you second question, you cannot create a SL#Removed without having a parent instance available. With one, it is as simple as sl1.Removed(1).
Your ScanListProcessor could be rewritten to take the ScanList it operates on as a value:
class ScanListProcessor[E](val sl: ScanList[E]) {
def process(msg: sl.Command) = {
msg match {
case sl.Remove(x) => sl.Removed(x)
case sl.Recover => ???
}
}
}
But this is awkward, and nested types probably aren't needed here. If all you want to do is group some traits, classes etc. together in a namespace, then put them in a package or object instead of a trait or class. In this case you would need to move the type parameter E down onto Remove and Removed themselves:
object ScanList {
sealed trait Command
case object Recover extends Command
case class Remove[E](item: E) extends Command
sealed trait Event
case class Removed[E](item: E) extends Event
}

Related

Type-safety for Patternmatching on Parameters with Dependent Types in Scala

I am currently working with a type hierarchy which represents a tree and an accompanying type hierarchy that represents steps of root-to-node paths in that tree. There are different kinds of nodes so different steps can be taken at each node. Therefore, the node types have a type member which is set to a trait including all valid steps. Take the following example:
// Steps
sealed trait Step
sealed trait LeafStep extends Step
sealed trait HorizontalStep extends Step
sealed trait VerticalStep extends Step
object Left extends HorizontalStep
object Right extends HorizontalStep
object Up extends VerticalStep
object Down extends VerticalStep
// The Tree
sealed trait Tree {
type PathType <: Step
}
case class Leaf() extends Tree {
override type PathType = LeafStep
}
case class Horizontal(left: Tree, right: Tree) extends Tree {
override type PathType = HorizontalStep
}
case class Vertical(up: Tree, down: Tree) extends Tree {
override type PathType = VerticalStep
}
In this example, given a tree the path Seq(Up, Right) would tell us to go to the "right" child of the "up" child of the root (assuming the tree's nodes have the fitting types). Of course, navigating the tree involves a bunch of code using PartialFunctions that is not shown in the example. However, in that process I want to provide a type-safe callback which is notified for every step that is taken and its arguments include both the step and the respective tree nodes.
My current approach is a function def callback(from: Tree, to: Tree)(step: from.PathType). That works without problems on the caller side, but I'm running into an issue when actually implementing such a callback that works with the data passed to it.
def callback(from: Tree, to: Tree)(step: from.PathType) = {
from match {
case f: Horizontal => step match {
case Left => ??? // do useful stuff here
case Right => ??? // do other useful stuff here
}
case _ => ()
}
}
In the function, the compiler is not convinced that Left and Right are of type from.PathType. Of course, I could simply add .asInstanceOf[f.PathType] and the code seems to work with that. However, the outer match gives us that f is of type Horizontal. We know that PathType of Horizontal objects is HorizontalStep and since f is identical to from we also know that from.PathType is of that same type. Finally, we can check that both Left and Right extend HorizontalStep. So, the above code should always be type-safe even without the cast.
Is that reasoning something the Scala compiler just does not check or am I missing a case where the types could differ? Is there a better way to achieve my goal of a type-safe callback? I'm using Scala 2.12.15
I'm afraid I do not have a thorough explanation on why this does not typecheck. (It seems as if for such dependent-typed parameters, the compiler just isn't able to apply the knowledge that was gained from pattern matching onto the other parameter (that was not explictly matched)).
Nevertheless, here is something that would work:
First, use a type parameter instead of a type member:
// (Steps as above)
// The Tree
sealed trait Tree[PathType <: Step]
// type alias to keep parameter lists simpler
// where we do not care..
type AnyTree = Tree[_ <: Step]
case class Leaf() extends Tree[LeafStep]
case class Horizontal(left: AnyTree, right: AnyTree) extends Tree[HorizontalStep]
case class Vertical(up: AnyTree, down: AnyTree) extends Tree[VerticalStep]
Then, this would fully typecheck:
def callback[S <: Step, F <: Tree[S]](from: F, to: AnyTree)(step: S) = {
def horizontalCallback(from: Horizontal)(step: HorizontalStep) = {
step match {
case Left => ??? // do useful stuff here
case Right => ??? // do other useful stuff here
}
}
from match {
case f: Horizontal =>
horizontalCallback(f)(step)
case _ =>
()
}
}
Confusingly, the compiler would not be able to properly check the pattern match on the step if one places it directly in the outer match like this (Edit: that is only true for 2.12 where this does not give a "match may not be exhaustive" warning - with 2.13 or 3.2, this checks fine):
def callback[S <: Step, F <: Tree[S]](from: F, to: AnyTree)(step: S) = {
from match {
case f: Horizontal =>
step match {
case Left => ??? // do useful stuff here
case Right => ??? // do other useful stuff here
}
case _ =>
()
}
}

Access field from trait in companion object

I have something like the following code (I simplified it):
trait A {
val CONST_VALUE = 10
}
class B(someValue: Int, values: Array[Int]) extends A {
//some methods
}
object B {
def apply(someValue: Int) = B(someValue, Array.ofDim[Array[Byte]](someValue).map(block => Array.fill[Byte](A.CONST_VALUE)(0)))
}
Basically, I declared a constant CONST_VALUE in the trait A. I am trying to use it in the companion object B to instantiate the class B. However, I can't access A.CONST_VALUE from the companion object B.(I'm getting a compilation error).
So how could I do this?
You can't do this.
First of all, object B is the companion object to class B, not to trait A. Companions need to have the same name and be defined in the same compilation unit.
Secondly, CONST_VALUE is an instance field of trait A. It is a member of an instance of A, not a member of A.
Thirdly, when you say A.CONST_VALUE you are basically calling the method CONST_VALUE on A. But you can only call methods on objects/values. A is not an object, it is a type, types and values live in different worlds, you cannot mix the two.
And fourth, your CONSTANT_VALUE is misleadingly named: only final vals are constant value definitions, so your CONSTANT_VALUE is not actually a constant value.
Fifth, your apply method calls itself (B() is syntactic sugar for B.apply()), and thus needs a return type annotation.
Sixth, your apply method calls itself with two arguments, but it is defined with only one parameter.
Seventh, you create an Array[Array[Byte]], but it is not clear to me why you want to do that and what you need it for.
That's a whole truckload of problems (especially considering that there are only a handful of lines of code to begin with), which you need to fix one-by-one. Here's one possible partial solution, but it is not clear to me what it is exactly that you are trying to achieve.
trait A
object A {
final val CONST_VALUE = 10
}
class B(someValue: Int, values: Array[Int]) extends A {
//some methods
}
object B {
def apply(someValue: Int): B = new B(
someValue,
Array.ofDim[Array[Byte]](someValue).map(block => Array.fill[Byte](A.CONST_VALUE)(0)))
}
Declare val CONST_VALUE = 10 inside the companion object A instead of trait A. Also corrected the apply method definition in object B
trait A {
}
object A {
final val CONST_VALUE = 10
}
class B(someValue: Int, values: Array[Int]) extends A {
//some methods
}
object B {
def apply(someValue: Int) = new B(someValue, Array.ofDim[Int](someValue).flatMap(block => Array.fill[Int](A.CONST_VALUE)(0)))
}

Checking derived class arguments in Scala

Assume the following pair of classes:
class A(arg:String)
class B(argList:Vector[String]) extends A(argList.first)
I want to be able to check for argList being empty before providing the base class constructor with its first element. Unfortunately, placing that check in the default constructor for B (e.g through require, as shown here) is way too late, since the base class' constructor will need to be called first.
This is probably a more general OOP question, but the solution is likely to be Scala-specific.
What do you expect to pass if argList is empty? In any case, you could just use the following:
class B(argList:Vector[String]) extends A(argList.headOption.getOrElse("your default string here")
One way to deal with this is via a companion object. You can mark the constructor for B as private to ensure no-one can by-pass the check, then add a suitable apply method to the companion object that pre-checks the input value(s):
class A(arg:String)
class B private(argList:Vector[String]) extends A(argList.head)
object B {
def apply(argList:Vector[String]): B = argList.headOption.map(_ => new B(argList)).getOrElse(throw new RuntimeException("Oops"))
}
Usage examples:
scala> B(Vector("foo", "bar"))
res2: B = B#328e9109
scala> B(Vector())
java.lang.RuntimeException: Oops
at B$$anonfun$apply$2.apply(<console>:24)
...
Note that for simplicity's sake, I simply throw an exception when handling bad data, but would probably try some other way of handling this situation (a default value per #Zoltan's answer is one such way).
That's why in most places constructors are replaced by factory objects. In Scala it's idiomatic to use companion object as such factories.
class A(arg: String)
abstract class B(arg: String) extends A(arg) {
def argList: IndexedSeq[String]
}
object B {
case object Empty extends B("") {
def argList = IndexedSeq.empty
}
case class NonEmpty private[B](argList: Vector[String]) extends B(argList.head)
def apply(argList: Vector[String]) =
if (argList.isEmpty) Empty else NonEmpty(argList)
def unapplySeq(b:B): Option[IndexedSeq[String]] = b match {
case Empty ⇒ Some(IndexedSeq.empty)
case NonEmpty(args) ⇒ Some(args)
}
}
you could verify that
B(Vector()) == B.Empty
B(Vector("x", "y")).isInstanceOf[B.NonEmpty]

Find the outer class when provided an instance of the inner class

Is there a way to get the parent class from an instance of an inner class using macros rather than run-time reflection?
I have a set of classes like this:
trait IdProvider {
type IdObject = Id.type
case class Id(underlying: Int)
}
case class SomeEntity(id: SomeEntity.Id)
object SomeEntity extends IdProvider
And some code that works with arbitrary IdProvider#Ids:
val lookup = Map[IdProvider#IdObject, Set[Operation]]
def can(operation: Operation, id: IdProvider#Id): Boolean = {
val idObject = findIdTypeFromInstance(id) // This is what I don't have
lookup.get(idObject).exists(s => s(operation))
}
Taking a leaf out of this gist by Paul P. I now have this macro:
def findIdTypeFromInstance[T <: AnyRef : c.WeakTypeTag](
c: blackbox.Context)(thing: c.Expr[T]): c.Expr[T] = {
import c.universe._
val companion = thing.actualType.typeSymbol.companion match {
case NoSymbol =>
c.abort(c.enclosingPosition, s"Instance of ${thing.actualType} has no companion object")
case sym => sym
}
def make[U: c.WeakTypeTag] = c.Expr[U](internal.gen.mkAttributedRef(companion))
make(c.WeakTypeTag(companion.typeSignature))
}
This works for simpler cases (top level case classes, classes and objects, and even nested case classes). However, when dealing with the IdProvider setup above the macro tries to generate this tree:
Select(This(TypeName("IdProvider")), TermName("Id"))
This results in an extremely long stack trace in my test, which starts with:
scala.reflect.internal.Types$TypeError: value is not a member of my.spec.MacroSpec
I have not been able to find a path from the instance or the companion (IdProvider#Id) to the parent class (in this case SomeEntity). Is there a way to get to SomeEntity or do I have to use run-time reflection?
The Id companion is basically a lazy val. You need the enclosing instance to retrieve its value because it's not a statically defined stable path.
With -Yshow-syms you can see it get added in mixin phase:
object SomeEntity
constructor SomeEntity
* method Id$lzycompute (private)
method apply (case <synthetic>)
value id
method readResolve (private <synthetic>)
method unapply (case <synthetic>)
value x$0 (<synthetic>)
* object Id (<synthetic> <stable>)
value <local SomeEntity>
* variable Id$module (private <local> <synthetic>)
The $outer field of an Id is added in explicitouter.
Is it easier just to expose the companion reference explicitly?
case class Id(underlying: Int) {
def c = Id
}
This is just a quick look; maybe there's a clever way to do it.

How to write a Service cappable of handling multiple parameter types in Scala?

I am designing an API. It basically looks like this:
trait Service {
def performUseCase[T](transferObjects: Iterable[TransferObject[T]])
}
trait TransferObject[T] {
def data: T
}
case class TransferObjectA[T](data: T) extends TransferObject[T]
case class TransferObjectB[T](data: T) extends TransferObject[T]
Note: If necessary I could change the implementation of the TransferObjects. Semantically important is:
There are at least two kinds of TransferObjects.
The transfer objects must build a sequential order in any way. It is not so important right now how that order is build: a Seq[TransferObject[_], or one transfer object referencing the next or however.
There will be different implementations of the Service trait. For each Service instance it must be specified which types this instance can handle.
Example: This Service val myService = MyService() can handle TransferObjects[MyTypeA] and TransferObjects[MyTypeB] etc.. When an API user tries to pass a TransferObjects[MyUnknownType] the compiler should throw an error.
I read about Scala's type system, about type classes (e.g. with implicits) and type declarations, but I don't understand all details, yet.
I tried to use an implementation of the Service trait with type specific handlers as type classes. MyHandler[MyTypeA], MyHandler[MyTypeB] etc. and using implicits to pass in the correct handler for the current parameter type. But the handlers should not be exposed to the API user. That is why I wonder if it is possible at all, to throw compiler errors if the user passes in parameters of types that the Service can't handle.
The real implementation is more complicated and currently broken. Maybe I can deliver some more code later.
Round-up:
So again: I want multiple Service instances. Each of them should be able to handle multiple parameter types. If a parameter of an unknown type is passed in, the compiler should throw an error. Type specific handlers are considered as implementation details and should remain hidden from API users.
How do I realize that?
you could use sealed trait TransferObject[T]{... and case class TransferObjectA(data: Int) extends TransferObject[Int] ... and inside of def performUseCase[T](transferObjects: Iterable[TransferObject[T]]) some
transferObject match{
case TransferObjectA(myInt) => ...
...
}//throws warning because of unmatched TransferObjectB on compile time because of sealed trait
also have a look at What is a sealed trait?
Here's a type class-based approach:
trait TransferObject[T] {
def data: T
}
case class TransferObjectA[T](data: T) extends TransferObject[T]
case class TransferObjectB[T](data: T) extends TransferObject[T]
trait Service {
protected[this] trait Handler[A] {
def apply(objs: Iterable[TransferObject[A]]): Unit
}
def performUseCase[A](objs: Iterable[TransferObject[A]])(
implicit handler: Handler[A]
) = handler(objs)
}
And then:
object MyIntAndStringService extends Service {
implicit object intHandler extends Handler[Int] {
def apply(objs: Iterable[TransferObject[Int]]) {
objs.foreach(obj => printf("Int: %d\n", obj.data))
}
}
implicit object stringHandler extends Handler[String] {
def apply(objs: Iterable[TransferObject[String]]) {
objs.foreach(obj => printf("String: %s\n", obj.data))
}
}
}
val ints = List(1, 2, 3).map(TransferObjectA(_))
val strings = List("A", "B", "C").map(TransferObjectA(_))
val chars = List('a', 'b', 'c').map(TransferObjectA(_))
And finally:
scala> MyIntAndStringService.performUseCase(ints)
Int: 1
Int: 2
Int: 3
scala> MyIntAndStringService.performUseCase(strings)
String: A
String: B
String: C
scala> MyIntAndStringService.performUseCase(chars)
<console>:14: error: could not find implicit value for parameter handler: MyIntAndStringService.Handler[Char]
MyIntAndStringService.performUseCase(chars)
^
As desired.