Question

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?

Was it helpful?

Solution

Definition of 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).

Implementation of 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.

OTHER TIPS

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):

  1. 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];

  2. 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]
}

This is a tough one to find, as the PolyN classes in shapeless are auto-generated via Boilerplate.scala.

All except for Poly0, which you can see here

In short... It's just a method on Poly1

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top