什么是 shapeless(scala)中的 "at"?

14

我在Shapeless的源代码和使用Shapeless的代码中看到了一个名为“at”的对象(可能是函数)。特别地,在回答这个问题时使用了它。下面是代码片段:

object iterateOverHList extends Poly1 {
  implicit def iterable[T, L[T] <: Iterable[T]] = at[L[T]](_.iterator)
}

我有一些线索表明它与~>类型的apply方法有关。 "at"具体是做什么的,它在哪里定义?


2
理论上,这是我关于多态函数值博客系列的第三部分的主题...总有一天我会写的;-) 在你等待的时候,@senia的答案非常好。 - Miles Sabin
3个回答

11

PolyN#at的定义

at是一种与Poly一起工作的通用方式。

~>applyPoly1特殊情况。这里使用apply来定义使用at的隐式方法:

implicit def caseUniv[T] = at[F[T]](apply(_))

方法atPolyN(例如在Poly1中)中定义,如下所示:链接

trait PolyN extends Poly { outer =>
  type Case[T1, T2, ..., TN] = poly.Case[this.type, T1 :: T2 :: ... :: TN :: HNil]
  object Case {
    type Aux[T1, T2, ..., TN, Result0] = poly.Case[outer.type, T1 :: T2 :: ... :: TN :: HNil] { type Result = Result0 }
  }

  class CaseBuilder[T1, T2, ..., TN] {
    def apply[Res](fn: (T1, T2, ..., TN) => Res) = new Case[T1, T2, ..., TN] {
      type Result = Res
      val value = (l: T1 :: T2 :: ... :: TN :: HNil) => l match {
        case a1 :: a2 :: ... :: aN :: HNil => fn(a1, a2, ..., aN)
      }
    }
  }

  def at[T1, T2, ..., TN] = new CaseBuilder[T1, T2, ..., TN]
}

如果遇到 Poly1 的情况:

trait Poly1 extends Poly { outer =>
  type Case[T1] = poly.Case[this.type, T1 :: HNil]
  object Case {
    type Aux[T1, Result0] = poly.Case[outer.type, T1 :: HNil] { type Result = Result0 }
  }

  class CaseBuilder[T1] {
    def apply[Res](fn: (T1) => Res) = new Case[T1] {
      type Result = Res
      val value = (l: T1) => l match {
        case a1 :: HNil => fn(a1)
      }
    }
  }

  def at[T1] = new CaseBuilder[T1]
}

所以at[Int]创建了一个CaseBuilder[Int]的实例,而at[Int].apply[String](_.toString)或者at[Int](_.toString)apply方法调用的语法糖)则创建了一个poly.Case[this.type, Int :: HNil]{ type Result = String }的实例。
因此,通过implicit def iterable[T, L[T] <: Iterable[T]] = at[L[T]](_.iterator),你可以创建一个隐式方法来创建一个poly.Case[this.type, L[T] :: HNil]{ type Result = Iterator[T] }
这个隐式方法被用在map中(还有一些其他的多态函数)。

HList#map的实现

map定义如下:
def map(f : Poly)(implicit mapper : Mapper[f.type, L]) : mapper.Out = mapper(l)

(LHList类型)

为了创建一个Mapper编译器,查找Case1[Fn, T]

对于A :: B :: ... :: HNil上的map(f),编译器必须查找Case1[f.type, A]Case1[f.type, B]等隐式参数。

List[Int] :: HNil的情况下,唯一需要的隐式Case1Case1[f.type, List[Int]]

请注意,Case1是这样定义的:

type Case1[Fn, T] = Case[Fn, T :: HNil]

因此,我们需要为Case[f.type, List[Int] :: HNil]找到一个隐式值。

如果f是一个object,编译器在搜索隐式值时会在f的字段和方法中查找。因此编译器会找到f中定义的Case


7

我不是专业人士,所以@miles-sabin和@travis-brown可以给出完整且更清晰的答案,但我也可以尝试一下(这并不完整,也没有展示所有正式问题):

  1. iterateOverHList是一个多态函数,扩展了Poly1,如果你查看此(Poly1)特质的实现,你会发现它只接受一个对象作为参数,类型为你的例子中的L[T]

  2. 函数at的确意味着(请看下面的实现):“在L[T]类型的情况下应用at内部的函数”,因此在你的例子中是你对象的iterator方法。 因此,您可以编写不同的隐式函数,可应用于不同的类型,当您使用不同和复杂的类型遍历HList(例如使用map)时,这非常有用。

您可以在此处找到Poly特质的实现以及我的上述说法的证明,例如:http://xuwei-k.github.io/shapeless-sxr/shapeless-2.10-2.0.0-M1/polyntraits.scala.html 在这里,我们可以看到Poly1特质是:

trait Poly1 extends Poly { outer =>
    type Case[A] = poly.Case[this.type, A :: HNil]
    object Case {
        type Aux[A, Result0] = poly.Case[outer.type, A :: HNil] { type Result = Result0 }
    }

    class CaseBuilder[A] {
        def apply[Res](fn: (A) => Res) = new Case[A] {
            type Result = Res
            val value = (l : A :: HNil) => l match { case a :: HNil => fn(a) }
        }
    }

    def at[A] = new CaseBuilder[A]
}

2
这是一个比较难找的,因为shapeless中的PolyN类是通过Boilerplate.scala自动生成的。
除了Poly0之外,你可以在这里看到所有的类。
简而言之,它只是Poly1上的一个方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接