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