破译最难的 scala 方法原型之一(光滑)



看看下面 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扩展到普通Rimplicit ClassTag[R] - 这是一个"上下文绑定",它只是语法糖。 ClassTag 是编译器生成的东西,存在于每个(具体(类型中,并有助于反射,因此这是一个提示,该方法可能会在某个时候对R进行一些反射。

现在,肉:这是一种通用方法,由两种类型参数化:[R, U] 。它的参数是两个函数,f: U => Rg: R => Option[U] 。这看起来有点像功能Prism概念 - 从UR的转换总是有效的,以及从RU的转换有时不起作用。

签名(有点(的有趣部分是最后的implicit shapeShape被描述为"typeclass",所以最好将其视为"约束":它将我们可以调用此函数的可能类型UR限制为仅那些具有适当Shape可用的类型。

查看Shape的文档,我们看到四种类型分别是LevelMixedUnpackedPacked。所以约束是:必须有一个Shape,其"级别"是FlatShapeLevel的某个子类型,其中Mixed类型是TUnpacked类型是R(Packed类型可以是任何类型(。

因此,这是一个类型级函数,表示RT的"解压缩版本"。再次使用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=StringU=EmailAddress,我们提供了两个方向的转换函数:f: EmailAddress => Stringg: 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,所以我不知道我对这个<>的用途有多准确 - 我做对了吗?

最新更新