0
votes

I am trying to convert a java webapp into scala. I been struggling with Type Mismatch errors which is not very helpful in determining cause of problem or I don't know how to read them. I have tried asking about issue in whole design in following post but looks like that's not helping me.
https://stackguides.com/questions/32573360/scala-type-mismatch-errors-when-using-type-classes-and-factory-methods

So I am breaking down my questions to small pieces. Hopefully that will lead to final solution. Can someone help me resolve contract between DataContext and DataContextBuilder?

Parts taken from above post:

trait DBObject
trait AggDBObject extends DBObject 
trait RawDBObject extends DBObject 
class Dimensions1Agg extends AggDBObject 
class Dimensions2Agg extends AggDBObject 
class Dimensions1Raw extends RawDBObject

trait IDataContext[A <: DBObject]  {
    var XsummaryData: A = _
    var XByDateData : Map[String, A] = _
    ... more feilds ..

    def restrictAccess = {.. some impl ..}
}

trait IDataContextBuilder[A <: DBObject] {
    def initDataPoints(dataContext: IDataContext[A]): Unit
}
class Dimension1AggContextBuilder extends IDataContextBuilder[Dimension1Agg] {
 .. override method impl..
}


object IDataContext {
  def apply(sId: Int, mId: Int): DataContext[_ <: DBObject]  = { 

    (sId, mId) match {

      case (1, 1) => {
        new DataContext[Dimension1Agg]()
      }
      case (1, 2)  => {
        new DataContext[Dimension1Raw]()
      }
    }
  }
}

Do I have right return type of apply method for object I am creating? If I remove it then I get some complex return type at call site "val dataContext: DataContext[_ >: Dimension1Agg with Dimension1Raw <: DBObject] with Product with Serializable { .. }"

object IDataContextBuilder {

  def apply(sId: Int, daId: Int): IDataContextBuilder[_ <: DBObject]  = {

    (sId, daId) match {

      case (1, 1) => {
        new Dimension1AggContextBuilder

      }
      case (1, 2) => {
        new Dimension1RawContextBuilder
      }
    }
  }
}

Do you see any issue in general with above factory methods?

I get following error when passing that DataContext to DataContextBuilder

type mismatch; found : IDataContext[_$1] where type $1 <: DBObject required: IDataContext[$19]

Is _$1 and _$19 above because scalac can't determine types?

EDIT:
Call site it a DataService class that receive user request params, orchestrate all above components and compose a response. I know its not functional but I am trying to do incremental refactoring. DataService check request params, passes that information to factory methods to create mutliple DataContext and DataContextBuilder and DataWorker; Call all the DataContextBuilder's initDataPoints methods, wait for them to finish; call all the Dataworker's generateView method, wait for them to finish, finally compose response.

  1. initialize a map of each measure to DataContext var measureToDCMap = MapInt, IDataContext[_ <: DBObject]

  2. for each measuer it performs following to populate DataContext

    Get concrete DataContext from its factory method Get concrete DataContextBuilder from its factory method Call DataContextBuilder's initDataPoints method

  3. all DataContext is populated with corosponding DimensionData at this point.

  4. initialize dataView map for each view of each measure for each metrix(avg tweets per day, avg tweets per topic, tweet rate percentile etc)

var dataView = MapInt, Map[Int, Map[Int, List[DataView]]] 5) for each view it performs following to generate final viewable response

Get concrete ViewWorker . i.e. Metrix1ViewWorker or MetrixViewWorker
Call getData method
  1. generate final response
2
so what IS your design? Can you paste the Type Mismatch error and sample code? Your question is not very clear :-( - Daniel Langdon
sorry I hit submit in middle of composing. gimme few mins - nir
@DanielL. let me know if I am missing any required information - nir
Seems a very convoluted way to use existential types on interfaces, but at the end of the day you are not USING the interfaces (no polymorphism). Most of them seem redundant too. There is an answer below for your type errors, but I would revisit what it is you need for the design piece. Would it suffice with a function that given two ints gives you a DataContext of the right type? - Daniel Langdon
I agree. IDataContext interface was initially created so that I can create different types of static class Dimension1DataContext, Dimension2DataContext etc but I prefer not to go that route. Any suggestion for removing redundancy and make it more clearer? And Yes a function that given two ints gives me DataContext of right type is what I am looking for. Also that return type should be compatible with initDataPoints method of IDataContextBuilder. - nir

2 Answers

1
votes

Here is a way to simplify it a little. I took out some extra interfaces, etc:

trait DBObject
trait AggDBObject extends DBObject
trait RawDBObject extends DBObject
class Dimensions1Agg extends AggDBObject
class Dimensions2Agg extends AggDBObject
class Dimensions1Raw extends RawDBObject

class DataContext[A <: DBObject] {
  var XsummaryData: A = _
  var XByDateData : Map[String, A] = _
  def restrictAccess = {
    // .. some impl ..
  }
}

object DataContext {

  def buildDim1Agg() : DataContext[Dimensions1Agg] = {
    // Put the custom code for Dim1 you had at `initDataPoints`
    // ...
    new DataContext[Dimensions1Agg]()
  }

  // Add other builder methods if you have lots of custom code, or functions shared across builders.

  /** This is your main builder function. 
    * Note that per your request, it is not polymorphic, but tied to a specific type of DBObject.  */
  def apply(sId: Int, mId: Int): DataContext[_ <: DBObject]  = {

    (sId, mId) match {
      case (1, 1) => buildDim1Agg()
      case (1, 2)  => buildDim1Agg()
        //... add more cases and a default case.
    }
  }
}

You did not post the code that would make a call to all this and some context is missing, so I'm making a lot of assumptions.

In particular, if you where willing to do actual OOP here you could move the custom code for each DBObject class into the constructor for that specific class (each class nows how to initialize itself) and have a more generic DataContext that does not care what specific type it stores:

trait DBObject
trait AggDBObject extends DBObject
trait RawDBObject extends DBObject

class Dimensions1Agg extends AggDBObject {
  // Put the custom code you had at `initDataPoints`
}

class Dimensions2Agg extends AggDBObject {
  // Put the custom code you had at `initDataPoints`
}

class Dimensions1Raw extends RawDBObject {
  // Put the custom code you had at `initDataPoints`
}


class DataContext(var summaryData: DBObject) {

  var XByDateData : Map[String, DBObject] = _

  def restrictAccess = {
    // .. some impl ..
  }
}

object DataContext {
  /** This is your main builder function.
    * This version IS polymorphic, your data context holds a DBObject of the right type, but callers don't need to know which one. */
  def apply(sId: Int, mId: Int): DataContext  = {

    (sId, mId) match {
      case (1, 1) => new DataContext(new Dimensions1Agg())
      case (1, 2)  => new DataContext(new Dimensions2Agg())
      //... add more cases and a default case.
    }
  }
}

Hope it helps.

0
votes

Anyway, I get following error when passing that DataContext to DataContextBuilder

type mismatch; found : IDataContext[_$1] where type $1 <: DBObject required: IDataContext[$19] is _$1 and _$19 because scalac can't determine types?

In this case DataContext.apply could return a DataContext[Dimension1Agg] while IDataContextBuilder.apply returned a Dimension1RawContextBuilder. This won't happen if you pass the same arguments to both, but the types allow it and so scalac correctly rejects the program. What I'd do in this case is avoid existential return types, e.g.

object DataContext {
  def apply(sId: Int, mId: Int)[A <: DBObject]: DataContext[A] = ...

  // or separate methods returning DataContext[Dimension1Agg] etc
}

if possible.