@MiloXia
2016-04-14T16:37:52.000000Z
字数 11271
阅读 1926
scala
blog:
http://milessabin.com/blog/2011/12/19/shapeless-preview/
http://kanaka.io/blog/2015/11/09/shapeless-not-a-tutorial-part-1.html
stackoverflow:
https://stackoverflow.com/search?q=user:334519+[shapeless]
import shapeless.{HList, HNil, ::}
how to use: https://github.com/milessabin/shapeless/blob/master/core%2Fsrc%2Ftest%2Fscala%2Fshapeless%2Fhlist.scala
trait Fruit
case class Apple() extends Fruit
case class Pear() extends Fruit
type FFFF = Fruit :: Fruit :: Fruit :: Fruit :: HNil
type APAP = Apple :: Pear :: Apple :: Pear :: HNil
val a: Apple = Apple()
val p: Pear = Pear()
val apap: APAP = a :: p :: a :: p :: HNil
val ffff: FFFF = apap // APAP <: FFFF
// discard precise typing
val ffff: FFFF = apap.unify
val apap2: Option[APAP] = ffff.cast[APAP]
apap2.get == apap
val mySecondHList : String :: Int :: java.util.UUID :: HNil = "foo" :: 42 :: java.util.UUID.randomUUID() :: HNil
mySecondHList.head // foo
mySecondHList.tail //
42 :: 49fa9131-eb09-4a14-b86e-e358b25bfbc7 :: HNil
mySecondHList.tail.tail.tail.head //HNil.head 会报错-编译不过
val l1 = 2 :: "a" :: HNil
l1.toList // List(2, a)
l1.reverse
//Prepend :::
l1 ::: l2
import nat._
println(l1(_0), l1(_1)) // (2,a)
println(l1.drop(_0)) //2 :: a :: true :: HNil
println(l1.drop(_1)) //a :: true :: HNil
println(l1.take(_1)) //2 :: HNil
println(l1.split(_0)) //(HNil,2 :: a :: true :: HNil)
println(l1.split(_1)) //(2 :: HNil,a :: true :: HNil)
还有filter,update, zip,diff 等各种操作
how to use: https://github.com/milessabin/shapeless/blob/master/core%2Fsrc%2Ftest%2Fscala%2Fshapeless%2Fzipper.scala
import syntax.zipper._
val l1 = 2 :: "a" :: true :: HNil
//Traversal
val z1 = l1.toZipper
println(z1) //Zipper(HNil,2 :: a :: true :: HNil,None)
println(z1.get) //2
println(z1.right.get) //a
println(z1.right.right.get) // true
val z2 = l1.toZipper.last
println(z2) // Zipper(true :: a :: 2 :: HNil,HNil,None)
println(z2.left.get) //true
println(z2.first.get) //2
//Update
println(z1.right.put("wibble").reify) // 2 :: wibble :: true :: HNil
println(z1.right.delete.reify) // 2 :: true :: HNil
println(z1.insert("bar").reify) // bar :: 2 :: a :: true :: HNil
println(z1.right.modify(_.toUpperCase).reify) // 2 :: A :: true :: HNil
//TypeIndexing 等于 hlist的selectManyType
println(z1.rightTo[Boolean].get) // true
//NatIndexing 等于 hlist的select
println(z1.rightBy(1).get) // a
val hlist1 = 1 :: "foo" :: true :: HNil
val hlist2 = "foo" :: 2 :: true :: HNil
def thirdIsTrue(hlist: HList) = hlist match {
case (_:Int) :: (_:String) :: true :: HNil => true
case _ => false
}
thirdIsTrue(hlist1) //true
val t1 = (23, "foo", 2.0, true)
val l1 = t1.hlisted
h1 == 23 :: "foo" :: 2.0 :: true :: HNil
val t2 = l1.tupled
t1 == t2
//polymorphic function -- 也是个自然变换
object choose extends (Set ~> Option) {
def apply[T](s: Set[T]) = s.headOption
}
//KList’s (HList’s whose elements share a common outer type constructor)
type SISS = Set[Int] :: Set[String] :: HNil
type OIOS = Option[Int] :: Option[String] :: HNil
val sets: SISS = Set(1) :: Set("foo") :: HNil
val opts: OIOS = sets map choose
println(opts, opts == Option(1) :: Option("foo") :: HNil)
//(Some(1) :: Some(foo) :: HNil,true)
val l1 = 2 :: "a" :: HNil
object test extends (Id ~> Const[Int]#λ) { //或写成 (Id ~>> Int)
def apply[T](s: T): Int = 0
}
println(l1 map test) // 0 :: 0 :: HNil
import poly._
val l1 = 2 :: "a" :: true :: HNil
object incInt extends (Int >-> Int)(_ + 1)
val l11 = l1 flatMap incInt
println(l11) // 3 :: HNil
type M0 = Int :: String :: Boolean :: HNil
val m0 = 13 :: "bar" :: false :: HNil
val l = 23 :: "foo" :: true :: HNil
val a0 = l.align(m0)
assertTypedEquals[M0](23 :: "foo" :: true :: HNil, a0)
object combine extends Poly {
implicit def caseCharString = use((c : Char, s : String) => s.indexOf(c))
implicit def caseIntBoolean = use((i : Int, b : Boolean) => if ((i >= 0) == b) "pass" else "fail")
}
val c1a = combine('o', "foo") // 1
val c1b = combine(c1a, true) //pass
println(c1a, c1b)
val lc = "foo" :: true :: HNil
val f1 = lc.foldLeft('o')(combine)
println(f1)
//验证类型上是否正确
val tf3 = implicitly[LeftFolder[String :: Boolean :: HNil, Char, combine.type]]
how to use: https://github.com/milessabin/shapeless/blob/master/core%2Fsrc%2Ftest%2Fscala%2Fshapeless%2Ftypeable.scala
import syntax.typeable._
import java.{ lang => jl }
val l: Any = 23L
val cl = l.cast[Long]
//BoxedPrimitives
val i: Any = 23
val ci = i.cast[jl.Integer]
//Unerased
val li: Any = List(1, 2, 3, 4)
val cli = li.cast[List[Int]]
val lvs: Any = List(Vector("foo", "bar", "baz"), Vector("wibble"))
val clvs = lvs.cast[List[Vector[String]]]
//HList
val lisdb: Any = 23 :: "foo" :: false :: HNil
val clisdb = lisdb.cast[Int :: String :: Boolean :: HNil]
assertTrue(clisdb.isDefined)
//Coproduct
type CP = Int :+: String :+: Double :+: Boolean :+: CNil
val cpi: Any = Coproduct[CP](23)
val ccpi = cpi.cast[CP]
assertTrue(ccpi.isDefined)
实现原理:http://milessabin.com/blog/2012/05/10/shapeless-polymorphic-function-values-2/
没有part3, part3就是现在的Case模式(type specific cases)吧
type specific cases 原理: http://kanaka.io/blog/2015/11/09/shapeless-not-a-tutorial-part-1.html
atT 返回一个shapeless.poly.Case[F,T::HNil]
We can now see how the apply method works for Poly1 :
For any Poly1 subtype F, given a argument of type T, an instance ofshapeless.poly.Case[F,T::HNil] is searched in the implicit scope.
If we have defined a parameterless implicit method in object F that returnsatT (f being a function defined on T), this method will be selected since it returns an instance of shapeless.poly.Case[F, T::HNil] and
The result of applying F to an argument of type T will be the one of applyingf to that argument.
//如上的combine,choose,test, incInt
object zero extends Poly0 {
implicit val zeroInt = at(0)
implicit val zeroDouble = at(0.0)
implicit val zeroString = at("")
implicit def zeroList[T] = at[List[T]](Nil)
}
object size extends Poly1 {
implicit def default[T] = at[T](_ => 1)
implicit def caseInt = at[Int](_ => 1)
implicit def caseString = at[String](_.length)
implicit def caseList[T] = at[List[T]](_.length)
implicit def caseOption[T](implicit st : Case.Aux[T, Int]) = at[Option[T]](t => 1+(t map size).getOrElse(0))
//Case.Aux[T, Int] is Case[I]{ type Result = O }
implicit def caseTuple[T, U](implicit st : Case.Aux[T, Int], su : Case.Aux[U, Int]) = at[(T, U)]{ case (t, u) => size(t)+size(u) }
}
//可测 implicitly[size.Case[Int]]
//~> 继承自Poly1,表示自然变换,F[_] -> G[_]
//~>> 是 type ~>>[F[_], R] = ~>[F, Const[R]#λ] 不需要打λ符号
object plus extends Poly2 {
implicit val caseInt = at[Int, Int](_ + _)
implicit val caseDouble = at[Double, Double](_ + _)
implicit val caseString = at[String, String](_ + _)
implicit def caseList[T] = at[List[T], List[T]](_ ::: _)
}
plus(1, 2)
object isd extends Poly3 {
implicit val default = at[Int, String, Double] { //default只是个名字,没啥特别的
case (i, s, d) => s"i: $i, s: $s, d: $d"
}
}
isd(1, "foo", 2.0)
it provides the hlist representation of a case class (or a tuple) and operations to convert an instance of the case class to its hlist representation, and back.
case class User(id: Long, name: String)
val genUser = Generic[User]
println(genUser.to(User(1L, "Homer"))) //1 :: Homer :: HNil
println(genUser.from(2L :: "Marge" :: HNil)) //User(2,Marge)
42L的类型是Long(42),编译期私有
record = fields的hlist
field = label (singleton type) + type (value type) = FieldType[K, +V] = V with KeyTag[K, V] (K is label)
how to use : https://github.com/milessabin/shapeless/blob/master/core%2Fsrc%2Ftest%2Fscala%2Fshapeless%2Frecords.scala
val field = 1 ->> Some("value")
//Some[String] with shapeless.labelled.KeyTag[Int(1),Some[String]] = Some(value)
case class Book(author: String, title: String, id: Int, price: Double)
val tapl = Book("Benjamin Pierce", "Types and Programming Languages", 262162091, 44.11)
val taplRecord =
("author" ->> "Benjamin Pierce") ::
("title" ->> "Types and Programming Languages") ::
("id" ->> 262162091) ::
("price" ->> 44.11) ::
HNil
val v1 = taplRecord.get("author")
represents case classes as records, using singleton-typed Symbols as keys (field names)
case class Rectangle(width: Int, height: Int)
val genRectangle = LabelledGeneric[Rectangle]
val repr = genRectangle.to(Rectangle(1,3))
println(repr) //1 :: 3 :: HNil
println(repr.get('width)) //1
val modified = repr.updated('height, 42)
println(modified) //1 :: 42 :: HNil
println(genRectangle.from(modified)) //Rectangle(1,42)
how to use: https://github.com/milessabin/shapeless/blob/master/core%2Fsrc%2Ftest%2Fscala%2Fshapeless%2Flenses.scala
import lens._
case class Address(street : String, city : String, postcode : String)
case class Person(name : String, age : Int, address : Address)
val address = Address("Southover Street", "Brighton", "BN2 9UA")
val person = Person("Joe Grey", 37, address)
//create lenses
val nameLens: Lens[Person, String]
val ageLens: Lens[Person, Int]
val addressLens: Lens[Person, Address]
val streetLens: Lens[Person, String]
val cityLens: Lens[Person, String]
val postcodeLens: Lens[Person, String]
val age1 = ageLens.get(person) //37
val person2 = ageLens.set(person)(38)
val street1 = streetLens.get(person) //"Southover Street"
//Compose
val addressLens_2 = lens[Person] >> 2
val streetLens_2 = lens[Address] >> 0
val personStreetLens1 = streetLens compose addressLens
val street1 = personStreetLens1.get(person) //"Southover Street"
//Tuples
type ISDB = (Int, (String, (Double, Boolean)))
val tp = (23, ("foo", (2.0, false)))
val lens0 = lens[ISDB] >> 0
val lens1 = lens[ISDB] >> 1
val lens10 = lens[ISDB] >> 1 >> 0
val lens11 = lens[ISDB] >> 1 >> 1
val lens110 = lens[ISDB] >> 1 >> 1 >> 0
val lens111 = lens[ISDB] >> 1 >> 1 >> 1
val i = lens0.get(tp)
//HLists
type ISB = Int :: String :: Boolean :: HNil
val l = 23 :: "foo" :: true :: HNil
val lens0 = hlistNthLens[ISB, _0]
val lensI = hlistSelectLens[ISB, Int]
val lens1 = hlistNthLens[ISB, _1]
val lensS = hlistSelectLens[ISB, String]
val lens2 = hlistNthLens[ISB, _2]
val lensB = hlistSelectLens[ISB, Boolean]
val i = lens0.get(l)
//Records & Sets & Maps
how to use: https://github.com/milessabin/shapeless/blob/master/core%2Fsrc%2Ftest%2Fscala%2Fshapeless%2Fcoproduct.scala
type ISB = Int :+: String :+: Boolean :+: CNil
val foo1 = Coproduct[ISB](23)
val foo2 = Coproduct[ISB]("foo")
val foo3 = Coproduct[ISB](true)
val sel1a = foo1.select[Int] // Some(23)
val sel1b = foo1.select[String] // None
def typed[A](a: A) = true
def cpMatch(v: ISB) = v match {
case Inl(x) =>
typed[Int](x); x
case Inr(Inl(x)) =>
typed[String](x); x
case Inr(Inr(Inl(x))) =>
typed[Boolean](x); x
case Inr(Inr(Inr(_))) => ??? // This impossible case required for exhaustivity
}
println(cpMatch(foo1)) //23
//flatMap & map
val foo1b = foo1 map size
println(foo1b) // 1
object coIdentity extends Poly1 {
implicit def default[A] = at[A](a => Coproduct[A :+: CNil](a))
}
val foo1c = foo1.flatMap(coIdentity)
//Here's an off-the-cuff, mostly untested solution. First for the type class:
import shapeless._, labelled.{ FieldType, field }
trait FromMap[L <: HList] {
def apply(m: Map[String, Any]): Option[L]
}
//And then the instances:
trait LowPriorityFromMap {
implicit def hconsFromMap1[K <: Symbol, V, T <: HList](implicit
witness: Witness.Aux[K],
typeable: Typeable[V],
fromMapT: FromMap[T]
): FromMap[FieldType[K, V] :: T] = new FromMap[FieldType[K, V] :: T] {
def apply(m: Map[String, Any]): Option[FieldType[K, V] :: T] = for {
v <- m.get(witness.value.name)
h <- typeable.cast(v)
t <- fromMapT(m)
} yield field[K](h) :: t
}
}
object FromMap extends LowPriorityFromMap {
implicit val hnilFromMap: FromMap[HNil] = new FromMap[HNil] {
def apply(m: Map[String, Any]): Option[HNil] = Some(HNil)
}
implicit def hconsFromMap0[K <: Symbol, V, R <: HList, T <: HList](implicit
witness: Witness.Aux[K],
gen: LabelledGeneric.Aux[V, R],
fromMapH: FromMap[R],
fromMapT: FromMap[T]
): FromMap[FieldType[K, V] :: T] = new FromMap[FieldType[K, V] :: T] {
def apply(m: Map[String, Any]): Option[FieldType[K, V] :: T] = for {
v <- m.get(witness.value.name)
r <- Typeable[Map[String, Any]].cast(v)
h <- fromMapH(r)
t <- fromMapT(m)
} yield field[K](gen.from(h)) :: t
}
}
//And then a helper class for convenience:
class ConvertHelper[A] {
def from[R <: HList](m: Map[String, Any])(implicit
gen: LabelledGeneric.Aux[A, R],
fromMap: FromMap[R]
): Option[A] = fromMap(m).map(gen.from(_))
}
def to[A]: ConvertHelper[A] = new ConvertHelper[A]
And the example:
case class Address(street: String, zip: Int)
case class Person(name: String, address: Address)
val mp = Map(
"name" -> "Tom",
"address" -> Map("street" -> "Jefferson st", "zip" -> 10000)
)
//And finally:
scala> to[Person].from(mp)
res0: Option[Person] = Some(Person(Tom,Address(Jefferson st,10000)))
//This will only work for case classes whose members are either Typeable or other case classes whose members are either Typeable or other case classes… (and so on).