在http://slick.typesafe.com/doc/2.1.0/api/index.html#scala.slick.lifted.ToShapedValue中查看以下scala slick类中的<>
方法,它让我想起了that iconic stackoverflow thread about scala prototypes。
def <>[R, U](f: (U) ⇒ R, g: (R) ⇒ Option[U])
(implicit arg0: ClassTag[R], shape: Shape[_ <: FlatShapeLevel, T, U, _]):
MappedProjection[R, U]
有人大胆且知识渊博,可以提供详尽的原型定义演示,仔细澄清所有类型的协方差/不变性,双参数列表和其他高级scala方面吗?
这项练习也将极大地帮助处理类似复杂的原型!
答案 0 :(得分:20)
好的,我们来看看:
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
被描述为“类型类”,因此最好将其视为“约束”:它限制了我们可以调用此函数的可能类型U
和R
,仅限适用Shape
的人。
查看the documentation forShape
,我们发现这四种类型有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
完全不受约束。
因此,我们可以通过在两个方向上提供转化功能,从任何MappedProjection[unpacked-version-of-T, U]
创建T
。因此,在简单版本中,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
。因此,这可能允许我们编写伪查询,例如'select from users where emailAddress.domain =“gmail.com”',这在数据库中不可能直接进行(不知道电子邮件地址的哪个部分是域名),但很容易在代码的帮助下完成。至少,这是我对它可能做的最好的猜测。
通过使用标准Prism
类型(例如Monocle中的类型)而不是明确地传递一对函数,可以使函数更清晰。使用隐式提供类型级函数是尴尬但必要的;在完全依赖类型的语言(例如Idris)中,我们可以将类型级函数编写为函数(类似def unpackedType(t: Type): Type = ...
)。从概念上讲,这个函数看起来像:
def <>[U](p: Prism[U, unpackedType(T)]): MappedProjection[unpackedType(T), U]
希望这能解释一些阅读新的,不熟悉的功能的思考过程。我根本不知道Slick,所以我不知道我对<>
的用途有多准确 - 我做对了吗?