I've seen an object (probably a function) called "at" sprinkled throughout the shapeless source and in code that uses shapeless. In particular, it is used in the answer to this other question. Here is the code snippet:
object iterateOverHList extends Poly1 {
implicit def iterable[T, L[T] <: Iterable[T]] = at[L[T]](_.iterator)
}
I've had some clue that it is related to the apply method of the ~> type. What specifically does "at" do, and where is it defined?
shapeless is a type class and dependent type based generic programming library for Scala.
A HList is a List where the type of every element is statically known at compile time. You may see them as "tuples on steroid". The beauty of HList compared to tuples is that you'll find all the essential List methods like take , head , tail , map , flatMap , zip , etc.
PolyN#at
at is a general way to work with Poly.
~> with apply is a special case of Poly1. apply here is used to define implicit method using at:
implicit def caseUniv[T] = at[F[T]](apply(_))
Method at is defined in PolyN (for instance in Poly1) like this:
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]
}
In case of 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]
}
So at[Int] creates an instance of CaseBuilder[Int] and at[Int].apply[String](_.toString) or just at[Int](_.toString) (synax sugar for apply method call) creates an instance of poly.Case[this.type, Int :: HNil]{ type Result = String }.
So with implicit def iterable[T, L[T] <: Iterable[T]] = at[L[T]](_.iterator) you create an implicit method to create a poly.Case[this.type, L[T] :: HNil]{ type Result = Iterator[T] }.
This implicit method is used in map (and in some other polymorphic functions).
HList#map
map is defined like this:
def map(f : Poly)(implicit mapper : Mapper[f.type, L]) : mapper.Out = mapper(l)
(L is the type of HList)
To create a Mapper compiler looks for Case1[Fn, T].
For map(f) on A :: B :: ... :: HNil compiler have to find implicits for Case1[f.type, A], Case1[f.type, B] and so on.
In case of List[Int] :: HNil the only implicit Case1 needed is Case1[f.type, List[Int]].
Note that Case1 is defined like this:
type Case1[Fn, T] = Case[Fn, T :: HNil]
So we have to find an implicit value for Case[f.type, List[Int] :: HNil].
In case f is an object one of the places to search for implicits is f fields and methods. So compiler will find Case defined in f.
I'm not a pro, so @miles-sabin and @travis-brown can give complete and more clear answer, but mb I can try too (it is not complete and doesnt show all formal issues):
iterateOverHList It is a polymorphic function, extends Poly1, and if u look implementations of this (Poly1) trait, you'll see that it takes as arguments only one object typed in your exmpl as L[T];
Function at exactly means (look the implementation below): "in case of type L[T] apply function inside at; so in your exmpl method iterator of your object.
so you can write different implicit functions which can be applied on different types, it is useful when u walk through a HList (with a map for example) with different and difficult types.
The implementation of Poly traits and proof of my words above you can find for example here: http://xuwei-k.github.io/shapeless-sxr/shapeless-2.10-2.0.0-M1/polyntraits.scala.html
Here we see that Poly1 trait is:
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]
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With