3

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?

Salim Fadhley
  • 6,975
  • 14
  • 46
  • 83

2 Answers2

3

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.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • "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." - can you point me to this alternative implementation? – Salim Fadhley Sep 23 '16 at 00:02
1

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
devkat
  • 1,624
  • 14
  • 15