Aux模式介绍

2021/07/01 Scala 共 1962 字,约 6 分钟
梦境迷离

Aux 模式介绍

Aux模式不是一种模式而是一种在每个库中使用的技术,它正在进行某种类型级别的编程,我们需要使用它来克服一个Scala限制。

每次我们在Scala中进行类型级计算时,我们都会在另一个类/特质中定义一个类型别名,让我们看一个例子:

trait Foo[A] {
  type B
  def value: B
}

例如,在这种情况下,类型级计算的结果将存储在B中。

所以让我们定义一些实例:

implicit def fi = new Foo[Int] {
  type B = String
  val value = "Foo"
}
implicit def fs = new Foo[String] {
  type B = Boolean
  val value = false
}

好吧,在这种情况下,我们没有做任何真正的计算,我们只是根据输入类型A改变B的类型,这足以理解Aux

现在在Scala中,我们可以使用 参数依赖类型 来访问在类/特质(路径依赖类型)中定义的类型,因此如果我们想在函数中使用类型B作为返回类型,我们可以这样做:

def foo[T](t: T)(implicit f: Foo[T]): f.B = f.value
val res1: String = foo(2)
val res2: Boolean = foo("")

在本例中,我们可以使用依赖类型和隐式参数来更改函数的返回类型,现在假设我们希望在下一个参数中使用此类型作为类型参数,例如获取该类型的Monoid实例:

import scalaz._, Scalaz._

def foo[T](t: T)(implicit f: Foo[T], m: Monoid[f.B]): f.B = m.zero

我们想这样做,但不幸的是我们得到了:

illegal dependent method type: parameter appears in the type of another parameter in the same section or an earlier one

Scala告诉我们不能在同一 section 中使用依赖类型,我们可以在下一个参数块中使用它或仅作为返回类型使用它。

这是我们的朋友Aux将要提供帮助的地方,让我们定义它:

type Aux[A0, B0] = Foo[A0] { type B = B0  }

我们在这里所做的是定义一个类型别名,其中A0映射到Foo AB0映射到类型type B,我一开始不明白的是关系类型B = B0 是双向的,所以如果我们用type B = Boolean来修复类型BB0也会得到这个类型。

所以现在我们可以这样写:

def foo[T, R](t: T)(implicit f: Foo.Aux[T, R], m: Monoid[R]): R = m.zero 
val res1: String = foo(2)
val res2: Boolean = foo("")

一个完整的例子:

import shapeless._

import scalaz._
import Scalaz._

object console extends App {

  trait Foo[A] {
    type B
    def value: B
  }

  object Foo {
    type Aux[A0, B0] = Foo[A0] { type B = B0 }

    implicit def fi = new Foo[Int] {
      type B = String
      val value = "Foo"
    }
    implicit def fs = new Foo[String] {
      type B = Boolean
      val value = false
    }

  }

  def ciao[T, R](t: T)
                (implicit f: Foo.Aux[T, R],
                          m: Monoid[R]): R = f.value
  val res = ciao(2)
  println(s"res: ${res}")
}

就是这样,基本上Aux只是一种提取类型级计算结果的方法,现在让我们看一个现实世界的例子,我想到的最常见的例子是shapeless Generic

def length[T, R <: HList](t: T)
                         (implicit g: Generic.Aux[T, R],
                                   l: Length[R]): l.Out = l()

case class Foo(i: Int, s: String, b: Boolean)
val foo = Foo(1, "", false)

val res = length(foo)
println(s"res: ${Nat.toInt(res)}")

// res: 3

在这种情况下,Generic.Aux将提取RT的通用表示,这样我们就可以使用它来解析我们用来获取HList长度的Length类型类。

总结

Aux是一种非常简单的技术,当你开始进行一些类型级编程时,它是强制性的,可能很简单,到目前为止没有人需要写一篇关于它的教程,但因为我花了一些精力来理解它,也许这可以帮助一些人。

原文 https://gigiigig.github.io/posts/2015/09/13/aux-pattern.html

文档信息

Search

    Table of Contents