Getting elements from an HList - scala

I toyed around with HList and the following works as expected:
val hl = 1 :: "foo" :: HNil
val i: Int = hl(_0)
val s: String = hl(_1)
However, I can't get the following piece of code working (let's assume for a moment random access on lists is a smart idea ;-)):
class Container(hl: HList) {
def get(n: Nat) = hl(n)
}
val container = new Container(1 :: "foo" :: HNil)
val i: Int = container.get(_0)
val s: String = container.get(_1)
I'd like to have get return an Int and String according to it's parameter. I assume, if possible at all, I have to use Aux or at but I'm not sure how to do this.

Try something along these lines,
scala> import shapeless._, nat._, ops.hlist._
import shapeless._
import nat._
import ops.hlist._
scala> class Container[L <: HList](hl: L) {
| def get(n: Nat)(implicit at: At[L, n.N]): at.Out = hl[n.N]
| }
defined class Container
scala> val container = new Container(1 :: "foo" :: HNil)
container: Container[shapeless.::[Int,shapeless.::[String,shapeless.HNil]]] = ...
scala> container.get(_0)
res1: Int = 1
scala> container.get(_1)
res2: String = foo
The first crucial difference here is that rather than typing hl as plain HList, which loses all specific information about the types of the elements, we parametrize over the precise type of the argument and preserve its structure as L. The second difference is that we use L to index the implicit At type class instance which is used to perform the indexing in get.
Also note that because there is an implicit conversion from Int literals to Nat's you can write,
scala> container.get(0)
res3: Int = 1
scala> container.get(1)
res4: String = foo

Related

How can I apply an HList of arbitrary functions to an arbitrary value?

I'd like to be able to apply an arbitrary list of Function1[I, ?]s to an arbitrary input I. This is what I have so far:
type StringInputFunction[T] = Function[String, T]
val strLen: String => Int = _.length
val strRev: String => String = _.reverse
val functions = strLen :: strRev :: HNil
val expected = 4 :: "evif" :: HNil
object applyTo5 extends (StringInputFunction ~> Id) {
override def apply[T](f: StringInputFunction[T]): Id[T] = f("five")
}
def applyFunctionsTo5[FH <: HList, OH <: HList](fs: FH)
(implicit constrain: UnaryTCConstraint[FH, StringInputFunction],
mapper: Mapper.Aux[applyTo5.type, FH, OH]): mapper.Out = {
fs.map(applyTo5)
}
applyFunctionsTo5(functions) shouldBe expected
class ApplyTo(string: String) extends (StringInputFunction ~> Id) {
override def apply[T](f: StringInputFunction[T]): Id[T] = f(string)
}
def applyFunctionsTo[FH <: HList, OH <: HList]
(fs: FH, input: String)
(implicit constrain: UnaryTCConstraint[FH, StringInputFunction],
mapper: Mapper.Aux[ApplyTo, FH, OH]): mapper.Out = {
val applyTo = new ApplyTo(input)
fs.map(applyTo)
}
applyFunctionsTo(functions, "five") shouldBe expected
This results in the compile error:
ShapelessSpec.scala:81: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[applyTo.type,FH]
fs.map(applyTo)
ShapelessSpec.scala:83: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper.Aux[ApplyTo,shapeless.::[String => Int,shapeless.::[String => String,shapeless.HNil]],OH]
applyFunctionsTo(functions, "five") shouldBe expected
How can I fix this to work with any String input?
Can I change genericise this even further to work with any input type T?
I was thinking I'd done this exact operation before, and was able to find this gist from a few years ago. To summarize my example there, you can do this pretty nicely using only operations that are already provided by Shapeless, in a manner that looks a lot like how you'd do something like this with ordinary lists at the value level. Suppose you have the following setup:
import shapeless.{ ::, HNil }
val strLen: String => Int = _.length
val strRev: String => String = _.reverse
val functions = strLen :: strRev :: HNil
Then you can write this:
scala> functions.zipApply(functions.mapConst("five"))
res0: Int :: String :: shapeless.HNil = 4 :: evif :: HNil
Or this:
scala> def foo(in: String) = functions.zipApply(functions.mapConst(in))
foo: (in: String)Int :: String :: shapeless.HNil
scala> foo("six")
res1: Int :: String :: shapeless.HNil = 3 :: xis :: HNil
This will work with any hlists of functions from a particular type applied to that particular type.
The gist gives a few alternative approaches, but zipApply plus mapConst feels by far the best to me.

Scala collection of classes with Implicit Ordering

I would like to create an Array (or List, ArrayBuffer, etc) which can only contain instance of classes with defined implicit Ordering (e.g. Int, Long, Double).
Something like this:
val ab = new ArrayBuffer[???]()
ab += 7
ab += 8.9
ab += 8L
I don't want to compare these values with each other.
Just use the type class constraint as shown below
def createList[T: Ordering](values: T*) = values.toList
T: Ordering implies only type which has Ordering instance in scope are allowed to be passed as arguments to the function.
scala> def createList[T: Ordering](values: T*) = values.toList
createList: [T](values: T*)(implicit evidence$1: Ordering[T])List[T]
scala> case class Cat()
defined class Cat
scala> createList(1, 2, 3)
res2: List[Int] = List(1, 2, 3)
scala> createList(Cat())
<console>:15: error: No implicit Ordering defined for Cat.
createList(Cat())
^
Integer ordering is available in scope but cat ordering is not available in scope. So you cannot pass Cat values until you provide instance of Ordering[Cat]
Now lets provide some fake ordering and see if compiler accepts Cat as argument
scala> implicit val orderingCat: Ordering[Cat] = (a: Cat, b: Cat) => ???
orderingCat: Ordering[Cat] = $anonfun$1#6be766d1
scala> createList(Cat())
res4: List[Cat] = List(Cat())
It works.
If you really want to have a list of objects of different types and still be able to statically check things about the objects in that list at compile time, you would have to use something like a HList from shapeless. Here is an example of how you could have two heterogeneous lists and still check at compile time that each ith element of both lists can be compared with each other.
import shapeless._
import shapeless.ops.hlist.{LiftAll, Zip, Mapper}
object lt extends Poly1 {
implicit def instance[A] = at[(Ordering[A], A, A)] {
case (ord, a, b) => ord.lt(a, b)
}
}
def areLessThan[L <: HList, O <: HList, OLL <: HList](a: L, b: L)(
implicit
ord: LiftAll.Aux[Ordering, L, O],
zip: Zip.Aux[O :: L :: L :: HNil, OLL],
map: Mapper[lt.type, OLL]
) = zip(ord.instances :: a :: b :: HNil).map(lt)
Using it:
scala> val a = 1 :: "b" :: Option(4L) :: HNil
a: Int :: String :: Option[Long] :: shapeless.HNil = 1 :: b :: Some(4) :: HNil
scala> val b = 2 :: "a" :: Option(7L) :: HNil
b: Int :: String :: Option[Long] :: shapeless.HNil = 2 :: a :: Some(7) :: HNil
scala> areLessThan(a, b)
res10: Boolean :: Boolean :: Boolean :: shapeless.HNil = true :: false :: true :: HNil

Scala Shapeless - Iterating/reading each item of Generic.Repr or converting it to HList

I'm learning shapeless and referring a tutorial from here. Which says,
Generic is a simple way to convert case class and product types (like
tuples) to HList, and vice-versa:
import shapeless.Generic
case class UserWithAge(name: String, age: Int)
val gen = Generic[UserWithAge]
val u = UserWithAge("Julien", 30)
val h = gen.to(u)
Now if I print h, I will get Julien :: 30 :: HNil. But, I'm unable to read each element from h such as h.head , h.tail will not work and there aren't any methods available in h. Here, h is type of gen.Repr and I couldn't figure out a way to convert it into HList either. So, how can I read each element from h?
In this case the type of gen.to(u) is gen.Repr, which if you look at the type of gen actually expends to String :: Int :: HNil, so it's already a HList!
scala> import shapeless.Generic
import shapeless.Generic
scala> case class UserWithAge(name: String, age: Int)
defined class UserWithAge
scala> val gen = Generic[UserWithAge]
gen: shapeless.Generic[UserWithAge]{type Repr = shapeless.::[String,shapeless.::[Int,shapeless.HNil]]} = anon$macro$3$1#4ff329b8
scala> val u = UserWithAge("Julien", 30)
u: UserWithAge = UserWithAge(Julien,30)
scala> val h = gen.to(u)
h: gen.Repr = Julien :: 30 :: HNil
scala> h.head
res0: String = Julien
scala> h.tail
res1: shapeless.::[Int,shapeless.HNil] = 30 :: HNil
In the general case, the Repr type of a Generic will be either a HList or a Coproduct. For examples of how to generically program over these, see shapeless-type-class-derivation-2015-demo.

Construct a type from HList of type classes

Given this code:
abstract class Col[A](val name: String)
case object Name extends Col[String]("name")
case object Age extends Col[Int]("age")
val cols = Name :: Age :: HNil
type TableDef = ??? // such that `type TableDef = String :: Int :: HNil`
How to derive TableDef in a generic way? I suspect it has to be a macro so that it can take arbitrary HList of Col[_].
You can get most of the way there with shapeless.ops.Comapped,
scala> import shapeless._, ops.hlist._
import shapeless._
import ops.hlist._
scala> Comapped[Col[String] :: Col[Int] :: HNil, Col]
res0: shapeless.ops.hlist.Comapped[shapeless.::[Col[String],shapeless.::[Col[Int],shapeless.HNil]],Col]{type Out = shapeless.::[String,shapeless.::[Int,shapeless.HNil]]} = shapeless.ops.hlist$Comapped$$anon$162#69bb4bc0
scala> type TableDef = res0.Out
defined type alias TableDef
scala> val foo: TableDef = "foo" :: 23 :: HNil
foo: TableDef = foo :: 23 :: HNil
scala> val foo: TableDef = "foo" :: true :: HNil
<console>:18: error: type mismatch;
found : shapeless.::[String,shapeless.::[Boolean,shapeless.HNil]]
required: TableDef
(which expands to) shapeless.::[String,shapeless.::[Int,shapeless.HNil]]
val foo: TableDef = "foo" :: true :: HNil
^
Note that this relies on the element types having Col as a direct outer type constructor. If you want to accommodate types like Name and Age which hide Col via a subtype then that would be possible via a variation on Comapped which takes subtypes into account.

Behavior of shapeless .toHList

The following compiles:
object Run1 extends App {
import shapeless._
import syntax.std.traversable._
case class Container[T](x: T)
Seq(Container(1), Container("x")).toHList[Container[Int] :: Container[String] :: HNil]
}
But this does not:
object Run2 extends App {
import shapeless._
import syntax.std.traversable._
class Container[T](val x: T)
Seq(new Container(1), new Container("x")).toHList[Container[Int] :: Container[String] :: HNil]
}
It fails with the following errors:
Error:(40, 52) could not find implicit value for parameter fl: shapeless.ops.traversable.FromTraversable[shapeless.:: [com.adaje.service.table.Run2.Container[Int],shapeless.::[com.adaje.service.table.Run2.Container[String],shapeless.HNil]]]
Seq(new Container(1), new Container("x")).toHList[Container[Int] :: Container[String] :: HNil]
^
Why does the second program not work and is there anything that can be added so that it does?
Thanks
The FromTraversable type class requires Typeable instances for the element types. Shapeless provides these off the shelf for case classes, but not for arbitrarily defined classes. You can define your own pretty easily, though:
import shapeless._, shapeless.syntax.std.traversable._
class Container[T](val x: T)
implicit def containerTypeable[A: Typeable]: Typeable[Container[A]] =
new Typeable[Container[A]] {
def cast(t: Any): Option[Container[A]] = t match {
case c: Container[_] => Typeable[A].cast(c.x).map(new Container(_))
case _ => None
}
def describe: String = s"Container[${ Typeable[A].describe }]"
}
And then:
scala> val cs = Seq(new Container(1), new Container("x"))
cs: Seq[Container[_ >: String with Int]] = List(Container#3c3c89b2, Container#1273a053)
scala> cs.toHList[Container[Int] :: Container[String] :: HNil]
res0: Option[shapeless.::[Container[Int],shapeless.::[Container[String],shapeless.HNil]]] = Some(Container#357c808b :: Container#607bcaf5 :: HNil)
And also:
scala> cs.reverse.toHList[Container[Int] :: Container[String] :: HNil]
res1: Option[shapeless.::[Container[Int],shapeless.::[Container[String],shapeless.HNil]]] = None
scalac's -Xlog-implicits option can be handy in cases like this—it'll make it clear that what was missing in your original non-case-class attempt was the Typeable instances.