Passing an extra argument into a polymorphic function?

断了今生、忘了曾经 提交于 2019-12-07 13:25:40

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!