问题
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?
回答1:
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.
回答2:
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
来源:https://stackoverflow.com/questions/39628068/passing-an-extra-argument-into-a-polymorphic-function