I have a polymorphic function which can turn lists into sets:
import shapeless.PolyDefns.~>
import shapeless._
val lists = List(1,2) :: List("A", "B") :: List(1.1, 2.2) :: HNil
object sss extends (List ~> Set) {
def apply[T](l:List[T]):Set[T] = {
l.toSet
}
}
lists.map(sss) // I want: Set(1,2) :: Set("A", "B") :: Set(1.1, 2.2) :: HNil
But what if I want to change the behavior of this function - I now want to add an extra argument which will specify which item in the input list should be put into the set. Here's an incorrect syntax - can you show me the correct way to do it?
object sss extends (List ~> Set) { // Compiler says no!
def apply[T](i:Int)(l:List[T]):Set[T] = {
l.slice(i,i+1).toSet
}
}
I think this is failing because the additional argument makes it no longer fit the signature of List ~> Set, so how can I overcome this?
There are a couple of workarounds for parametrizing a Poly, one of which is mentioned in the other answer, although the exact implementation there won't work. Instead you need to do this:
import shapeless._, shapeless.poly.~>
val lists = List(1, 2) :: List("A", "B") :: List(1.1, 2.2) :: HNil
class sss(i: Int) extends (List ~> Set) {
def apply[T](l: List[T]): Set[T] = l.slice(i, i+1).toSet
}
object sss1 extends sss(1)
lists.map(sss1)
…where the fact that sss1 is defined as an object (not a val) is necessary for the last line to compile.
That approach compiles, but it's not possible to use it in lots of contexts—e.g. you can't define your sss1 (or whatever) object in a method where the type of the hlist is generic.
Here's a slightly messier but more flexible workaround I've used before:
import shapeless._
val lists = List(1, 2) :: List("A", "B") :: List(1.1, 2.2) :: HNil
object sss extends Poly2 {
implicit def withI[T]: Case.Aux[List[T], Int, Set[T]] =
at((l, i) => l.slice(i, i + 1).toSet)
}
lists.zipWith(lists.mapConst(1))(sss)
// Set(2) :: Set(B) :: Set(2.2) :: HNil
Now you could actually write a method that took a generic L <: HList and the slice parameter i—you'd just need several implicit arguments to support the mapConst and zipWith applications.
Neither approach is very elegant, though, and I personally tend to avoid Poly most of the time—defining a custom type class is almost going to be cleaner, and in many cases required.
As you already pointed out, you cannot change the signature of the polymorphic function. But could create your function dynamically:
class sss(i: Int) extends (List ~> Set) {
def apply[T](l:List[T]): Set[T] = {
l.slice(i, i+1).toSet
}
}
val sss1 = new sss(1)
lists.map(sss1) // Set(2) :: Set(B) :: Set(2.2) :: HNil
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With