好的,让我们来看一下:
class ToShapedValue[T](val value: T) extends AnyVal {
...
@inline def <>[R: ClassTag, U](f: (U) ⇒ R, g: (R) ⇒ Option[U])(implicit shape: Shape[_ <: FlatShapeLevel, T, U, _]): MappedProjection[R, U]
}
这个类是一个包装器;虽然我并没有从快速浏览中看到implicit转换,但它看起来像是一种"pimp my library"模式。因此,我猜想这是为了将<><>作为"扩展方法"添加到某些(或者所有)类型中。
@inline是一个注释,一种在任何事物上放置元数据的方式;这是一种提示编译器应该进行内联处理的方法。<>是方法名 - 在scala中有很多像"运算符"的东西实际上只是普通的方法。
你链接的文档已经将R:ClassTag扩展成了普通的R和一个implicit ClassTag[R]——这是一种"上下文界定",它只是语法糖。ClassTag是一个由编译器生成的针对每个(具体)类型存在的东西,它有助于反射,因此,这是一种提示该方法可能会在某个时候对R进行一些反射处理的方法。
现在,进入正题:这是一个通用的方法,由两个类型参数化:
[R,U]
。它的参数是两个函数:
f: U => R
和
g: R => Option[U]
。这看起来有点像函数式
Prism
概念——从
U
到
R
的转换总是有效的,而从
R
到
U
的转换有时不起作用。
签名(有点)有趣的部分是末尾的
implicit shape
。
Shape
被描述为“typeclass”,因此最好将其视为“约束条件”:它限制了我们可以使用哪些适当的
Shape
类型来调用此函数的可能类型
U
和
R
。
查看Shape
的文档, 我们可以看到四种类型是 Level
, Mixed
, Unpacked
和 Packed
。因此,限制是:必须有一个Shape
,其“level”是FlatShapeLevel
的某个子类型,其中Mixed
类型为T
,Unpacked
类型为R
(Packed
类型可以是任何类型)。
因此,这是一个类型级函数,表达了R
是T
的“解包版本”的概念。再次使用Shape
文档中的示例,如果T
是(Column[Int],Column[(Int,String)],(Int,Option[Double]))
,那么R
将是(Int,(Int,String),(Int,Option[Double])
(仅适用于FlatShapeLevel
,但我会做出判断,认为这可能不重要)。有趣的是,U
完全没有限制。
这让我们能够通过在两个方向上提供转换函数,从任何类型的T创建一个MappedProjection[未打包版本的T, U]。因此,在简单的情况下,T可能是Column[String] - 数据库中字符串列的表示形式 - 我们想将其表示为某些特定于应用程序的类型,例如EmailAddress。因此,R=String,U=EmailAddress,并且我们在两个方向上提供转换函数:f: EmailAddress => String和g: String => Option[EmailAddress]。这样做是有意义的:每个EmailAddress都可以表示为String(至少如果我们想将它们存储在数据库中的话),但并非每个String都是有效的EmailAddress。如果我们的数据库在电子邮件地址列中某处有例如“
http://www.foo.com/”,那么我们的g将返回None,并且Slick可以优雅地处理这种情况。
MappedProjection
本身很遗憾没有文档。但是我猜它是一种可以查询的东西的一种懒惰表示方式;我们有一个Column[String]
,现在我们有了一个伪列,其(基础)类型为EmailAddress
。因此,这可能允许我们编写伪查询,例如“从用户中选择,其中emailAddress.domain =“gmail.com””,这在数据库中直接做是不可能的(数据库不知道电子邮件地址的哪个部分是域),但通过代码却很容易实现。至少,这是我对它可能做什么的最佳猜测。
可以说,如果使用标准的Prism
类型(例如来自Monocle的类型)而不是显式地传递一对函数,那么该函数可能会更清晰。使用implicit提供类型级函数是笨拙但必要的;在完全依赖类型的语言(例如Idris)中,我们可以将我们的类型级函数编写为函数(类似于def unpackedType(t: Type): Type = ...
)。因此,从概念上讲,这个函数看起来像:
def <>[U](p: Prism[U, unpackedType(T)]): MappedProjection[unpackedType(T), U]
希望这解释了一些阅读新的、不熟悉的函数的思考过程。我完全不了解Slick,所以我不知道这个
<>
的用途是否准确 - 我理解得对吗?