解密最棘手的scala方法原型之一(光滑)

时间:2014-12-22 10:26:27

标签: scala slick-2.0

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方面吗?

这项练习也将极大地帮助处理类似复杂的原型!

1 个答案:

答案 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扩展为普通Rimplicit ClassTag[R] - 这是一个“上下文绑定”,它只是语法糖。 ClassTag是一个编译器生成的东西,它存在于每个(具体)类型并有助于反射,所以这是一个提示,该方法可能会在某个时刻对R进行一些反思。

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

签名(有点)的有趣部分是最后的implicit shapeShape被描述为“类型类”,因此最好将其视为“约束”:它限制了我们可以调用此函数的可能类型UR,仅限适用Shape的人。

查看the documentation forShape,我们发现这四种类型有LevelMixedUnpackedPacked。因此约束条件是:必须有Shape,其“级别”是FlatShapeLevel的某个子类型,其中Mixed类型为TUnpacked类型为RPacked类型可以是任何类型)。

因此,这是一个类型级函数,表示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=StringU=EmailAddress,我们在两个方向提供转化功能:f: EmailAddress => Stringg: 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,所以我不知道我对<>的用途有多准确 - 我做对了吗?