在Scala中使用可变长度索引的“update”方法

时间:2012-11-16 21:58:16

标签: scala shapeless variadic-functions

我正在编写类似于具有可变维度的数组的代码。我所做的是维护线性底层集合并使用索引访问方法将其包装起来。由于数据结构的维度未知,我写了类似

的内容
def apply(i: Int*): Double = ...

它完美无缺。但是,我不能做同样的事情更新方法和运算符,如+ =,所以我最终编写像

这样的方法
def set(v: Double, i: Int*) ...
def add(v: Double, i: Int*) ...

这很好,但不是我真正想要的。我想有关更新的问题可以通过两种方式解决:

  1. 更改更新功能中参数的顺序,这使它看起来很奇怪。
  2. 允许变长参数不是最后一个。我发现这个问题在一般情况下提出,可以通过使用currying函数来解决,这里不适用。
  3. 关于+ =的问题似乎更复杂,甚至在索引具有固定长度时也存在。也许我们可以添加一个具有+ =运算符的对象并使用此(...)来获取对象(这样(...)+ = v将调用我们期望的某个方法),但这会与申请方法。

    如果有人解决上述任何问题,或者我们不能编写这样的代码,请分享您的想法!感谢〜

3 个答案:

答案 0 :(得分:5)

update在Scala中是一个相当特殊的人工制品,因为它主要是语法糖,并不符合任何特定的方法签名。这意味着我们可以创造性并为更新提供一个arity-polymorphic签名,

scala> class Indexed { def update[P <: Product](p: P) = p }
defined class Indexed

scala> val i = new Indexed
i: Indexed = Indexed@1ea0e836

scala> i(0) = 1.0
res0: (Int, Double) = (0,1.0)

scala> i(0, 1) = 1.0
res1: (Int, Int, Double) = (0,1,1.0)

scala> i(0, 1, 2) = 1.0
res2: (Int, Int, Int, Double) = (0,1,2,1.0)

scala> i(0, 1, 2, 3) = 1.0
res3: (Int, Int, Int, Int, Double) = (0,1,2,3,1.0)

就目前而言,这使得LHS上的索引类型和RHS上的值的类型完全不受约束,

scala> i(23, true, 'c') = "foo"
res4: (Int, Boolean, Char, String) = (23,true,c,foo)

但我们可以通过support for tuples 2.0.0-SNAPSHOT中的新shapeless提供的一些隐含证据来解决这个问题,

scala> import shapeless._
import shapeless._

scala> import syntax.tuple._
import syntax.tuple._

scala> class Indexed {
     |   def update[P <: Product, I](p: P)
     |     (implicit
     |       init:   TupleInit.Aux[P, I],
     |       toList: TupleToList[I, Int],
     |       last:   TupleLast.Aux[P, Double]) = (toList(init(p)), last(p))
     | }
defined class Indexed

scala> val i = new Indexed
i: Indexed = Indexed@76ab909a

scala> i(0) = 1.0
res10: (List[Int], Double) = (List(0),1.0)

scala> i(0, 1) = 2.0
res11: (List[Int], Double) = (List(0, 1),2.0)

scala> i(0, 1, 2) = 3.0
res12: (List[Int], Double) = (List(0, 1, 2),3.0)

scala> i(0, 1, 2, 3) = 4.0
res13: (List[Int], Double) = (List(0, 1, 2, 3),4.0)

scala> i(0, 1, 2) = "foo" // Doesn't compile
<console>:22: error: could not find implicit value for parameter
  last: shapeless.ops.tuple.TupleLast.Aux[(Int, Int, Int, String),Double]
              i(0, 1, 2) = "foo" // Doesn't compile
                         ^

scala> i(23, "foo", true) = 5.0 // Doesn't compile
<console>:22: error: could not find implicit value for parameter
  toList: shapeless.ops.tuple.TupleToList[I,Int]
              i(23, "foo", true) = 5.0 // Doesn't compile
                                 ^

答案 1 :(得分:2)

我现在看到的最简单的解决方案是为每个要支持的维度提供许多不同的update重载。假设您可以确定您将使用的最大尺寸为10,这意味着您将需要10次重载。这可能看起来不太实际,但我可以很容易地抽象出来,所以它实际上非常实用:

trait MultiKeyUpdate[K, V] {
  def doUpdate( k: K* )( v: V )
  def update(k1: K, v: V) { doUpdate( k1 )( v ) }
  def update(k1: K, k2: K, v: V) { doUpdate( k1, k2 )( v ) }
  def update(k1: K, k2: K, k3: K, v: V) { doUpdate( k1, k2, k3 )( v ) }  
  // ... and so on, up until max dimension ...
}

用法:

class C extends MultiKeyUpdate[Int, Double] {
  def apply(i: Int*): Double = {
    println("Returning element " + i.mkString("[",",","]"))
    123
  }
  def doUpdate( i: Int* )( v: Double ) {
    println("Updating element " + i.mkString("[",",","]") + " to value " + v)
  }
}

在REPL中进行了一些测试:

scala> val o = new C
o: C = C@12798c1
scala> o(1,2,3)
Returning element [1,2,3]
res3: Double = 123.0
scala> o(1,2,3) = 5.0
Updating element [1,2,3] to value 5.0
scala> o(1,2,3) += 7.0
Returning element [1,2,3]
Updating element [1,2,3] to value 130.0

答案 2 :(得分:1)

class SetMe {
  def set(i: Int*)(v: Double) { println("Set "+v+" with "+i.mkString(",")) }
}

scala> (new SetMe).set(4,7,19,3)(math.Pi)
Set 3.141592653589793 with 4,7,19,3

然而,update本身无法做到这一点。可能值得提交增强请求。