Search code examples
scalagenericsinference

Scala generics inference / lower bounds in a value declaration


I am fairly new to Scala and have been attempting to develop a data grid in Lift that would allow me to display data comfortably (it also serves me well as a kind of a Scala/Lift exercise in order to get a better grasp of the language). Anyway, I seem to be stuck on generics inference.

I have an abstract Grid class that all the concrete grids extend. They then define their columns via a columns field. I want to implement default sorting but cannot figure out how to make the necessary inference work properly. Here is the (very simplified) code:

//Lift classes
trait Mapper[A <: Mapper[A]]
trait MappedField[FieldType, OwnerType <: Mapper[OwnerType]]
final case class OrderBy[O <: Mapper[O], T](field: MappedField[T, O])

//models
class Items extends Mapper[Items] {
  object name extends MappedField[String, Items]
}
object Items extends Items

//Grid
abstract class Grid {
  case class Column[O <: Mapper[O]](val label: String, val alias: String, val column: MappedField[_, O])

  val columns: List[Column[_]]

  def sort(by: List[String]) = {
    //HERE I get the inferred type arguments do not conform to method apply's type parameter bounds...
    columns.filter(c => by.contains(c.alias)).map(c => OrderBy(c.column))
  }
}

class ItemsGrid extends Grid {
  val columns = List(new Column[Items]("Name", "name", Items.name))

  def sortWorking(by: List[String]) = {
    val col = new Column[Items]("Name", "name", Items.name)
    OrderBy(col.column)
  }
}

... which does not compile. I get the following error on the OrderBy's apply in the sort method in Grid: inferred type arguments [$2,$1] do not conform to method apply's type parameter bounds [O <: main.Mapper[O],T].

As the dummy sorting in sortWorking in Items is working just fine the problem has to be hidden in the List[Column[_]] in the abstract Grid. I have been unable to declare the type properly using neither lower bounds nor existentials and simply cannot convince the compiler to infer that as Column has the correct type, a list of them will too.

Any ideas or pointing me in the right direction is more than appreciated! Tadeáš


Solution

  • Issue in next line:

    val columns: List[Column[_]] // in '_'
    

    I propose to shift type parameter to type variable (use embedded polymorphism):

    //Grid
    abstract class Grid {
      trait Column {
        type F
        type O <: Mapper[O]   
    
        val label: String
        val alias: String
        val column: MappedField[F, O]
      }
    
      object Column {
        def apply[F1, O1 <: Mapper[O1]]
          (l : String, a : String, c : MappedField[F1, O1]) =
            new Column {
              type F = F1
              type O = O1
    
              val label = l
              val alias = a
              val column = c
            }
      }
    
      val columns: List[Column]
    
      def sort(by: List[String]) = {
        columns.filter(c => by.contains(c.alias)).map(c => OrderBy(c.column))
      }
    }
    

    And Grid instance will look like:

    class ItemsGrid extends Grid {
      val columns = List(Column("Name", "name", Items.name))
    
      def sortWorking(by: List[String]) = {
        val col = Column("Name", "name", Items.name)
        OrderBy(col.column)
      }
    }