Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is "at" in shapeless (scala)?

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?

like image 430
jcrudy Avatar asked Jan 03 '14 21:01

jcrudy


People also ask

What is shapeless Scala?

shapeless is a type class and dependent type based generic programming library for Scala.

What is an HList?

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.


2 Answers

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.

like image 196
senia Avatar answered Sep 24 '22 23:09

senia


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]
}
like image 43
DaunnC Avatar answered Sep 24 '22 23:09

DaunnC