将额外参数传递给多态函数?

3

我有一个多态函数,可以将列表转换为集合:

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

如果我想改变这个函数的行为,现在我想添加一个额外的参数来指定应将输入列表中的哪个项目放入集合中。下面是错误的语法 - 你能告诉我正确的做法吗?

object sss extends (List ~> Set) { // Compiler says no!
  def apply[T](i:Int)(l:List[T]):Set[T] = {
    l.slice(i,i+1).toSet
  }
}

我认为这个失败是因为额外的参数使它不再符合List ~> Set的签名,那么我该怎么解决呢?


有些问题:// 我想要:1 ::“A”:: 3.5 :: HNil - pedrofurla
2个回答

3

有几种方法可以将Poly参数化,其中一种在另一个答案中提到,但那里的具体实现不起作用。相反,您需要执行以下操作:

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)

…在最后一行编译的必要条件是sss1被定义为一个对象(而不是val)。

这种方法可以编译,但在很多情况下无法使用,例如您无法在类型为通用的hlist中定义您的sss1(或其他内容)对象。

以下是我以前使用过的略微混乱但更灵活的解决方法:

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

现在你可以编写一个方法,接受一个泛型 L <: HList 和切片参数 i——你只需要几个隐式参数来支持 mapConstzipWith 应用。

然而,这两种方法都不太优雅,我个人倾向于大多数情况下避免使用 Poly——定义一个自定义类型类通常会更加清晰,并且在许多情况下是必需的。


虽然这两种方法都不太优雅,但我个人倾向于大部分时间避免使用Poly——定义一个自定义类型类几乎总是更清晰的选择,并且在许多情况下是必需的。你能指导我如何实现这种替代方案吗? - Salim Fadhley

1
正如您已经指出的那样,您无法更改多态函数的签名。但是可以动态创建您的函数:
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

1
除非你将 sss1 变成一个对象(出于相当深奥的原因),否则这个代码无法正常工作。 - Travis Brown
奇怪,它在我的测试用例中可以工作(如果我创建一个保存实例的“val”)。 - devkat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接