TypeQuux ships with a rich API for operating on heterogenous lists and subsumes operations typically associated with klists. Operations that would result in runtime errors (mostly) do not compile. All indexed operations (apply
, drop
, take
etc) are 0-based and can be indexed from the left or the right.
The type signatures for hlists can be quite long and are not terribly informative in the examples presented below. /**/
represents an omitted (for clarity) type signature.
scala> import typequux._ // package
import typequux._
scala> import Typequux._ // useful imports
import Typequux._
scala> type LT = String :+: AnyVal :+: AnyRef :+: Traversable[_] :+: Option[Int] :+: HNil
defined type alias LT
scala> type ST = String :+: Long :+: Set[Double] :+: List[Int] :+: None.type :+: HNil
defined type alias ST
scala> implicitly[ST <:< LT] // compiles
res0: <:<[ST,LT] = <function1>
scala> val a = "mmm" :+: 42L :+: Set(3.14, 2.718) :+: List(1, 2, 3) :+: None :+: HNil
a: /**/ = mmm :+: 42 :+: Set(3.14, 2.718) :+: List(1, 2, 3) :+: None :+: HNil
scala> val l: LT = a
l: LT = mmm :+: 42 :+: Set(3.14, 2.718) :+: List(1, 2, 3) :+: None :+: HNil
scala> val s: ST = a
s: ST = mmm :+: 42 :+: Set(3.14, 2.718) :+: List(1, 2, 3) :+: None :+: HNil
scala> val a = 3 :+: "ai4" :+: List('r', 'h') :+: HNil; val b = '3' :+: 2 :+: 'j' :+: "sdfh" :+: HNil
a: /**/ = 3 :+: ai4 :+: List(r, h) :+: HNil
b: /**/ = 3 :+: 2 :+: j :+: sdfh :+: HNil
scala> val ab = a :++: b
ab: /**/ = 3 :+: ai4 :+: List(r, h) :+: 3 :+: 2 :+: j :+: sdfh :+: HNil
scala> val x = "str" :+: true :+: 1 :+: Some(3.14) :+: HNil
x: /**/ = str :+: true :+: 1 :+: Some(3.14) :+: HNil
scala> val xr = x.reverse
xr: /**/ = Some(3.14) :+: 1 :+: true :+: str :+: HNil
scala> val xrr = xr.reverse
xrr: /**/ = str :+: true :+: 1 :+: Some(3.14) :+: HNil
scala> x == xrr
res26: Boolean = true
scala> val hl1 = true :+: "foo" :+: 2 :+: Some("one is waiting for you") :+: HNil
hl1: /**/ = true :+: foo :+: 2 :+: Some(one is waiting for you) :+: HNil
scala> hl1.length
res1: Long = 4
scala> HNil.length
res2: Long = 0
scala> val p = 3 :+: true :+: "asdf" :+: false :+: 'k' :+: () :+: 13 :+: 9.3 :+: HNil
p: /**/ = 3 :+: true :+: asdf :+: false :+: k :+: () :+: 13 :+: 9.3 :+: HNil
scala> p(0) // note that the type information is preserved
res3: Int = 3
scala> p(4)
res4: Char = k
scala> p.right(0)
res5: Double = 9.3
scala> p.right(6)
res6: Boolean = true
scala> p(100) // does not compile
The arguments is the number of elements to drop.
scala> p.drop(0)
res7: /**/ = 3 :+: true :+: asdf :+: false :+: k :+: () :+: 13 :+: 9.3 :+: HNil
scala> p.drop(4)
res8: /**/ = k :+: () :+: 13 :+: 9.3 :+: HNil
scala> p.dropRight(4)
res10: /**/ = 3 :+: true :+: asdf :+: false :+: HNil
scala> p.dropRight(6)
res12: /**/ = 3 :+: true :+: HNil
The argument is the number of elements to take.
scala> p.take(0)
res13: /**/ = HNil
scala> p.take(4)
res14: /**/ = 3 :+: true :+: asdf :+: false :+: HNil
scala> p.takeRight(4)
res15: /**/ = k :+: () :+: 13 :+: 9.3 :+: HNil
scala> p.takeRight(8)
res16: /**/ = 3 :+: true :+: asdf :+: false :+: k :+: () :+: 13 :+: 9.3 :+: HNil
scala> val m = "p" :+: 3 :+: 't' :+: Some("is") :+: HNil
m: /**/ = p :+: 3 :+: t :+: Some(is) :+: HNil
scala> m.updated(2, 110)
res20: /**/ = p :+: 3 :+: 110 :+: Some(is) :+: HNil
scala> m.updated(0, 2.718)
res21: /**/ = 2.718 :+: 3 :+: t :+: Some(is) :+: HNil
scala> m.updatedRight(0, List("dogs", "cats"))
res22: /**/ = p :+: 3 :+: t :+: List(dogs, cats) :+: HNil
scala> m.updatedRight(2, None)
res24: /**/ = p :+: None :+: t :+: Some(is) :+: HNil
scala> m.remove(0)
res28: /**/ = 3 :+: t :+: Some(is) :+: HNil
scala> m.remove(3)
res29: /**/ = p :+: 3 :+: t :+: HNil
scala> m.removeRight(0)
res30: /**/ = p :+: 3 :+: t :+: HNil
scala> m.removeRight(2)
res31: /**/ = p :+: t :+: Some(is) :+: HNil
scala> m.indexMap(1, (i: Int) => i << 2)
res34: /**/ = p :+: 12 :+: t :+: Some(is) :+: HNil
scala> m.indexMapRight(1, (c: Char) => (c.toInt, c))
res35: /**/ = p :+: 3 :+: (116,t) :+: Some(is) :+: HNil
m.indexFlatMap(2, (c: Char) => c.toInt :+: (c.toString + "asty" :+: HNil))
res38: /**/ = p :+: 3 :+: 116 :+: tasty :+: Some(is) :+: HNil
scala> m.indexFlatMapRight(1, (c: Char) => Some(c.toString + "ense") :+: "Negotiations" :+: 42 :+: HNil)
res39: /**/ = p :+: 3 :+: Some(tense) :+: Negotiations :+: 42 :+: Some(is) :+: HNil
The index is the position at which the inserted element will go.
scala> m.insert(0, 3.14159)
res41: /**/ = 3.14159 :+: p :+: 3 :+: t :+: Some(is) :+: HNil
scala> m.insert(2, "2.718")
res42: /**/ = p :+: 3 :+: 2.718 :+: t :+: Some(is) :+: HNil
scala> m.insertRight(0, Some(6.62607))
res43: /**/ = p :+: 3 :+: t :+: Some(is) :+: Some(6.62607) :+: HNil
scala> m.insertRight(1, None)
res44: /**/ = p :+: 3 :+: t :+: None :+: Some(is) :+: HNil
The index is the position at which the head of the inserted hlist will go.
scala> m.insertM(0, true :+: "foo" :+: HNil)
res45: /**/ = true :+: foo :+: p :+: 3 :+: t :+: Some(is) :+: HNil
scala> m.insertM(2, true :+: "foo" :+: HNil)
res46: /**/ = p :+: 3 :+: true :+: foo :+: t :+: Some(is) :+: HNil
scala> m.insertMRight(0, true :+: "foo" :+: HNil) // essentially concatenation
res47: /**/ = p :+: 3 :+: t :+: Some(is) :+: true :+: foo :+: HNil
scala> m.insertMRight(2, true :+: "foo" :+: HNil)
res48: /**/ = p :+: 3 :+: true :+: foo :+: t :+: Some(is) :+: HNil
scala> val w = 3 :+: true :+: "asdf" :+: 'k' :+: () :+: 9.3 :+: HNil
w: /**/ = 3 :+: true :+: asdf :+: k :+: () :+: 9.3 :+: HNil
scala> w.splitAt(0)
res50: /**/ = (HNil,3 :+: true :+: asdf :+: k :+: () :+: 9.3 :+: HNil)
scala> w.splitAt(3)
res51: /**/ = (3 :+: true :+: asdf :+: HNil,k :+: () :+: 9.3 :+: HNil)
scala> w.splitAtRight(2)
res53: /**/ = (3 :+: true :+: asdf :+: k :+: HNil,() :+: 9.3 :+: HNil)
scala> w.splitAtRight(4)
res54: /**/ = (3 :+: true :+: HNil,asdf :+: k :+: () :+: 9.3 :+: HNil)
Type-indexes support at
, take
, drop
, update
, remove
, map
, flatmap
, element insertion
, hlist insertion
and splitAt
scala> w.t[String].at
res55: String = asdf
scala> w.t[String].before
res56: /**/ = 3 :+: true :+: HNil
scala> w.t[String].after
res57: /**/ = k :+: () :+: 9.3 :+: HNil
scala> w.t[String].drop
res58: /**/ = asdf :+: k :+: () :+: 9.3 :+: HNil
scala> w.t[String].take
res59: /**/ = 3 :+: true :+: HNil
scala> w.t[String].updated(19)
res60: /**/ = 3 :+: true :+: 19 :+: k :+: () :+: 9.3 :+: HNil
scala> w.t[Unit].remove
res61: /**/ = 3 :+: true :+: asdf :+: k :+: 9.3 :+: HNil
scala> w.t[Char].map(_.isUpper)
res62: /**/ = 3 :+: true :+: asdf :+: false :+: () :+: 9.3 :+: HNil
scala> w.t[String].flatMap(s => s(0) :+: s.substring(1) :+: HNil)
res63: /**/ = 3 :+: true :+: a :+: sdf :+: k :+: () :+: 9.3 :+: HNil
scala> w.t[String].insert(Some(4.4))
res64: /**/ = 3 :+: true :+: Some(4.4) :+: asdf :+: k :+: () :+: 9.3 :+: HNil
scala> w.t[String].insertM(Some(true) :+: None :+: HNil)
res65: /**/ = 3 :+: true :+: Some(true) :+: None :+: asdf :+: k :+: () :+: 9.3 :+: HNil
scala> w.t[Char].splitAt
res66: /**/ = (3 :+: true :+: asdf :+: HNil,k :+: () :+: 9.3 :+: HNil)
When indexing by type, if case multiple elements have the indexed type, the element furthest to the right is selected
scala> val ti = 3.14159 :+: "john" :+: List("puppy, kitten") :+: Some("snow") :+: "ramsay" :+: (22L, 11.14) :+: HNil
ti: /**/ = 3.14159 :+: john :+: List(puppy, kitten) :+: Some(snow) :+: ramsay :+: (22,11.14) :+: HNil
scala> ti.t[String].at
res68: String = ramsay
The length of the resulting list is the minimum of the length of the originals
scala> val rz = 3 :+: "ai4" :+: List('r', 'H') :+: HNil; val sz = '3' :+: 2 :+: 'j' :+: "sdfh" :+: HNil
rz: /**/ = 3 :+: ai4 :+: List(r, H) :+: HNil
sz: /**/ = 3 :+: 2 :+: j :+: sdfh :+: HNil
scala> rz zip sz
res69: /**/ = (3,3) :+: (ai4,2) :+: (List(r, H),j) :+: HNil
scala> sz zip rz
res70: /**/ = (3,3) :+: (2,ai4) :+: (j,List(r, H)) :+: HNil
scala> rz zip sz.tail
res71: /**/ = (3,2) :+: (ai4,j) :+: (List(r, H),sdfh) :+: HNil
scala> val tpls = (1, true) :+: (Some("string"), "99 bottles of beer on the wall") :+: ("oogachaka", 42L) :+: HNil
tpls: /**/ = (1,true) :+: (Some(string),99 bottles of beer on the wall) :+: (oogachaka,42) :+: HNil
scala> tpls.unzip
res72: /**/ = (1 :+: Some(string) :+: oogachaka :+: HNil,true :+: 99 bottles of beer on the wall :+: 42 :+: HNil)
scala> val list2Option: List ~> Option = new (List ~> Option) {override def apply[T](x: List[T]) = x.headOption}
list2Option: /**/ = /**/
scala> val hl1 = List(1, 2, 3) :+: List[Boolean]() :+: List("oogachaka", "ho gaya") :+: HNil
hl1: /**/ = List(1, 2, 3) :+: List() :+: List(oogachaka, ho gaya) :+: HNil
scala> hl1 transform list2Option
res73: /**/ = Some(1) :+: None :+: Some(oogachaka) :+: HNil
scala> HNil transform list2Option
res74: /**/ = HNil
scala> val list2Down: Vector ~> Id = new (Vector ~> Id) {override def apply[T](x: Vector[T]) = x(0)}
list2Down: /**/ = /**/
scala> val hl2 = Vector(1, 2, 1) :+: Vector(true) :+: HNil
hl2: /**/ = Vector(1, 2, 1) :+: Vector(true) :+: HNil
scala> hl2 down list2Down
res75: /**/ = 1 :+: true :+: HNil
scala> HNil down list2Down
res76: /**/ = HNil
Or you could yoda-apply it for kicks.
scala> val y1 = 9.75 :+: 'x' :+: HNil
y1: /**/ = 9.75 :+: x :+: HNil
scala> val y2 = -2.125 :+: 'X' :+: HNil
y2: /**/ = -2.125 :+: X :+: HNil
scala> val f = ((x: Double) => x + 5) :+: ((x: Char) => x.isUpper) :+: HNil
f: /**/ = <function1> :+: <function1> :+: HNil
scala> f fapply y1
res77: /**/ = 14.75 :+: false :+: HNil
scala> f fapply y2
res78: /**/ = 2.875 :+: true :+: HNil
scala> y1 yapply f // yoda-application: to the data, apply the function
res79: /**/ = 14.75 :+: false :+: HNil
scala> y2 yapply f
res80: /**/ = 2.875 :+: true :+: HNil
scala> val sz1 = List(1, 2, 3) :+: List(true, false) :+: HNil
sz1: /**/ = List(1, 2, 3) :+: List(true, false) :+: HNil
scala> val sz2 = Vector(1, 2, 3) :+: Vector(true, false) :+: Vector("alpha", "beta", "charlie", "delta") :+: Vector(3.14, 2.718, 6.262) :+: HNil
sz2: /**/ = Vector(1, 2, 3) :+: Vector(true, false) :+: Vector(alpha, beta, charlie, delta) :+: Vector(3.14, 2.718, 6.262) :+: HNil
scala> sz1.azipped
res81: /**/ = List(1 :+: true :+: HNil, 2 :+: false :+: HNil)
scala> sz2.azipped
res82: /**/ = Vector(1 :+: true :+: alpha :+: 3.14 :+: HNil, 2 :+: false :+: beta :+: 2.718 :+: HNil)
scala> sz1.zipwith { case i :+: b :+: _ => (b, i) }
res83: List[(Boolean, Int)] = List((true,1), (false,2))
scala> sz2.zipwith { case i :+: b :+: s :+: d :+: _ => ((i, s), (b, d))}
res84: scala.collection.immutable.Vector[((Int, String), (Boolean, Double))] = Vector(((1,alpha),(true,3.14)), ((2,beta),(false,2.718)))
scala> val fibs: Stream[BigInt] = BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map { n => n._1 + n._2}
fibs: Stream[BigInt] = Stream(0, ?)
scala> val nats: Stream[Int] = Stream.from(1)
nats: Stream[Int] = Stream(1, ?)
scala> val pows: Stream[Long] = {def go(n: Int): Stream[Long] = (1L << n) #:: go(n + 1); go(1) }
pows: Stream[Long] = Stream(2, ?)
scala> val fz1 = fibs :+: nats :+: HNil
fz1: /**/ = Stream(0, ?) :+: Stream(1, ?) :+: HNil
scala> val fz2 = fibs :+: nats :+: pows :+: HNil
fz2: /**/ = Stream(0, ?) :+: Stream(1, ?) :+: Stream(2, ?) :+: HNil
scala> val fz3 = fibs :+: nats :+: Stream.empty[String] :+: HNil
fz3: /**/ = Stream(0, ?) :+: Stream(1, ?) :+: Stream() :+: HNil
scala> fz1.azipped
res85: /**/ = Stream(0 :+: 1 :+: HNil, ?)
scala> (res85 take 5).toList
res87: /**/ = List(0 :+: 1 :+: HNil, 1 :+: 2 :+: HNil, 1 :+: 3 :+: HNil, 2 :+: 4 :+: HNil, 3 :+: 5 :+: HNil)
scala> fz2.azipped
res88: /**/ = Stream(0 :+: 1 :+: 2 :+: HNil, ?)
scala> (res88 take 3).toVector
res89: /**/ = Vector(0 :+: 1 :+: 2 :+: HNil, 1 :+: 2 :+: 4 :+: HNil, 1 :+: 3 :+: 8 :+: HNil)
scala> fz3.azipped
res90: /**/ = Stream()
scala> fz1.zipwith { case b :+: i :+: _ => (i, b) }
res91: Stream[(Int, BigInt)] = Stream((1,0), ?)
scala> (res91 take 3).toArray
res92: Array[(Int, BigInt)] = Array((1,0), (2,1), (3,1))
scala> fz2.zipwith { case b :+: i :+: l :+: _ => (i, (b, l)) }
res93: Stream[(Int, (BigInt, Long))] = Stream((1,(0,2)), ?)
scala> (res93 take 3).toList
res94: List[(Int, (BigInt, Long))] = List((1,(0,2)), (2,(1,4)), (3,(1,8)))
scala> fz3.zipwith { case b :+: i :+: s :+: _ => (i, b, s) }
res95: Stream[(Int, BigInt, String)] = Stream()
Provided that there exists an implicit conversion to a common type for each element type of a hlist, you can use foreach
, count
, exists
, forall
and foldleft
as you would with a standard library list.
scala> import language.implicitConversions
import language.implicitConversions
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait Lengthable {
def length: Int
}
object Lengthable {
implicit def int2Lengthable(i: Int): Lengthable = new Lengthable {
override def length = i
}
implicit def string2Lengthable(s: String): Lengthable = new Lengthable {
override def length = s.length
}
implicit def seq2Lengthable[T](s: Seq[T]): Lengthable = new Lengthable {
override def length = s.length
}
implicit def bool2Lengthable(b: Boolean): Lengthable = new Lengthable {
override def length = if (b) 1 else 0
}
implicit def tuple2Lengthable[T, U](t: (T, U))(implicit ev0: T => Lengthable, ev1: U => Lengthable): Lengthable =
new Lengthable {
override def length = t._1.length * t._2.length
}
}
// Exiting paste mode, now interpreting.
defined trait Lengthable
defined object Lengthable
scala> val cvt = 3 :+: "oogachaka" :+: List(1, 2, 3) :+: Vector("cow", "chicken") :+: false :+: (5, "ho gaya") :+: HNil
cvt: /**/ = 3 :+: oogachaka :+: List(1, 2, 3) :+: Vector(cow, chicken) :+: false :+: (5,ho gaya) :+: HNil
scala> var maxl = 0
maxl: Int = 0
scala> cvt.foreach[Lengthable](l => maxl = math.max(maxl, l.length))
scala> maxl
res98: Int = 35
scala> cvt.exists[Lengthable](_.length < 10)
res99: Boolean = true
scala> cvt.forall[Lengthable](_.length >= 0)
res100: Boolean = true
scala> cvt.forall[Lengthable](_.length > 0)
res101: Boolean = false
scala> cvt.count[Lengthable](_.length > 5)
res102: Int = 2
scala> cvt.foldLeft[Int, Lengthable](0)(_ + _.length)
res103: Int = 52
The element type of the list is the least upper bound of the element types of the HList
scala> val hlc1 = 42 :+: 22L :+: 3.14159 :+: 'c' :+: 2.718f :+: HNil
hlc1: /**/ = 42 :+: 22 :+: 3.14159 :+: c :+: 2.718 :+: HNil
scala> hlc1.toList
res105: List[AnyVal] = List(42, 22, 3.14159, c, 2.718)
scala> val hlc2 = List(1, 2, 3) :+: List(true, false) :+: HNil
hlc2: /**/ = List(1, 2, 3) :+: List(true, false) :+: HNil
scala> hlc2.toList
res106: List[List[AnyVal]] = List(List(1, 2, 3), List(true, false))
scala> val hlc3 = Some("foo") :+: Some(Set(1, 2,3)) :+: HNil
hlc3: /**/ = Some(foo) :+: Some(Set(1, 2, 3)) :+: HNil
scala> hlc3.toList
res107: List[Some[Object]] = List(Some(foo), Some(Set(1, 2, 3)))
scala> val hlc4 = Some("foo") :+: Some(Set(1, 2,3)) :+: None :+: HNil
hlc4: /**/ = Some(foo) :+: Some(Set(1, 2, 3)) :+: None :+: HNil
scala> hlc4.toList
res109: List[Option[Object]] = List(Some(foo), Some(Set(1, 2, 3)), None)