我正在尝试从Play框架中的对象列表中构建<table>
的通用方法。
我想创建一个代表metadatas列的类ColumnInfo
:
case class ColumnInfo[T](name: String, value: T => Any)
name
字段代表......嗯,列的名称,函数value
应该在参数中获取一个对象,并返回该列的值。
假设我有一个模型User
,扩展了另一个类(或特征,无论如何)Bean
:
case class User(name: String, age: Int) extends Bean
然后我创建一个Play Framework模板名称list.scala.html
,其中List[Bean]
和List[Column[Bean]]
为参数,并显示相应的<table>
:
@(list: List[Bean], columns: List[ColumnInfo[Bean]])
<table>
<thead>
<tr>
@for(c <- columns) {
<th>@c.name</th>
}
</tr>
</thead>
<tbody>
@for(obj <- list) {
<tr>
@for(c <- columns) {
<td>@c.value(obj)</td>
}
</tr>
}
</tbody>
</table>
在我的控制器的动作中,我应该有这样的事情:
object ListController extends Controller {
def list = Action {
val users = List(
User("foo", 20),
User("bar", 30)
)
val columns = List(
ColumnInfo[User]("Name", _.name),
ColumnInfo[User]("Age", _.age)
)
Ok(views.html.list(users, columns)
}
}
问题是我无法将ColumnInfo[User]
放入ColumnInfo[Bean]
的列表中!
这是正常的。但是,如果我在T
协变中创建ColumnInfo
类型,它会告诉我:
case class ColumnInfo[+T](name: String, value: T => Any)
covariant type T occurs in contravariant position in type => (T) => Any of value value
逻辑。但那我该怎么办?我也尝试使用下限,向U
添加其他类型ColumnInfo
,例如[+T, U >: T]
,但它只给我带来了其他错误。
非常感谢你的帮助!
答案 0 :(得分:2)
问题是ColumnInfo[User]
不是ColumnInfo[Bean]
。例如,如果你有
val myInfo = ColumnInfo[User]("MyCol", user => user.name)
val myBean = new Bean
myInfo.value(myBean)
由于myBean
没有name
方法(即使我们可以强制它进行编译,它会在运行时失败),因此无法工作,所以编译器捕获并将其抛出。
事实上,ColumnInfo
似乎在T
中是逆变的(任何进入函数的东西都是逆变的,由于示例中演示的原因 - 在某些语言中,它们实际上使用了关键字{{ 1}}对于逆变,要明确这一点。
因此,您可以定义in
,如:
ColumnInfo
不幸的是,这限制了模板的重复使用,因为其签名必须是case class ColumnInfo[-T](name: String, value: T => Any)
在理想的世界中,模板将支持类型参数,例如常规的Scala方法,因此您可以拥有类似@(list: List[User], columns: List[ColumnInfo[User]])
的签名。但是,Play templates do not currently support type parameters。
我可以通过两种方式看到这个
我们可以用存在类型来破解它。我们将模板的参数包装成一个不变的case类:
@[T](list: List[T], columns: List[ColumnInfo[T]])
并将模板的签名更改为:
case class TableData[T](list: List[T], columns: List[ColumnInfo[T]])
我们现在必须在模板中将@(cols: TableData[T forSome {type T}])
更改为list
并将cols.list
更改为columns
以匹配。
我们可以调用我们的模板:
cols.columns
或者,我们可以解决问题。为您的模板添加以下签名:
// In ListController...
Ok(views.html.list(TableData(users, columns)))
实际调用时,并将@(list: List[Any], columns: List[ColumnInfo[Any]])
投射到columns
:
List[ColumnInfo[Any]]
这将编译,因为Scala使用类型擦除。如果// In ListController...
Ok(views.html.list(users, columns.asInstanceOf[List[ColumnInfo[Any]]]))
实际上是list
,那么这些类型在运行时就是正确的。