看看下面 scala slick 类中的 <>
方法,从 http://slick.typesafe.com/doc/2.1.0/api/index.html#scala.slick.lifted.ToShapedValue 开始,它让我想起了关于 scala 原型的标志性 stackoverflow 线程。
def <>[R, U](f: (U) ⇒ R, g: (R) ⇒ Option[U])
(implicit arg0: ClassTag[R], shape: Shape[_ <: FlatShapeLevel, T, U, _]):
MappedProjection[R, U]
一个大胆而知识渊博的人能否提供这个长原型定义的清晰演练,仔细澄清其所有类型协方差/不变性、双参数列表和其他高级 scala 方面?
这个练习也将极大地帮助处理类似的复杂原型!
好的,让我们来看看:
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]
}
该类是一个AnyVal
包装器;虽然我实际上无法从快速查看中看到implicit
转换,但它闻起来像"皮条客我的库"模式。所以我猜这是为了将<>
作为"扩展方法"添加到某些(或可能是所有(类型上。
@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",所以最好将其视为"约束":它将我们可以调用此函数的可能类型U
和R
限制为仅那些具有适当Shape
可用的类型。
查看Shape
的文档,我们看到四种类型分别是Level
、Mixed
、Unpacked
和Packed
。所以约束是:必须有一个Shape
,其"级别"是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[unpacked-version-of-T, U]
。因此,在一个简单的版本中,也许T
是一个Column[String]
- 数据库中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 的类型(而不是显式传递一对函数,可以使函数更清晰。使用隐式提供类型级函数很尴尬,但很有必要;在完全依赖的类型语言(例如 Idris(中,我们可以将类型级函数编写为函数(类似于 def unpackedType(t: Type): Type = ...
(。所以从概念上讲,这个函数看起来像这样:
def <>[U](p: Prism[U, unpackedType(T)]): MappedProjection[unpackedType(T), U]
希望这能解释一些阅读新的、不熟悉的功能的思维过程。我根本不了解 Slick,所以我不知道我对这个<>
的用途有多准确 - 我做对了吗?