6

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

def apply(i: Int*): Double = ...

而且效果很好。但是,我不能做同样的事情来更新方法和运算符,如 +=,所以我最终编写了类似的方法

def set(v: Double, i: Int*) ...
def add(v: Double, i: Int*) ...

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

  1. 更改更新函数中的参数顺序,这使它看起来很奇怪。
  2. 允许不作为最后一个变长参数。我发现这个问题是在一般环境中提出的,可以通过使用柯里化函数来解决,这在此处不适用。

+= 的问题似乎更复杂,甚至在索引是固定长度时也存在。也许我们可以添加一个具有 += 运算符的对象并使用 this(...) 来获取该对象(这样 this(...) += v 将调用一些我们期望的方法),但这会与应用方法。

如果有人对上述任何问题有解决方案,或者有理由说明我们不应该编写这样的代码,请分享您的想法!谢谢~

4

3 回答 3

6

update在 Scala 中是一个相当特殊的人工制品,因为它主要是语法糖,不对应于任何特定的方法签名。这意味着我们可以发挥创造力并为更新提供一个多态签名,

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)

但是我们可以通过在无形2.0.0-SNAPSHOT中对元组的新支持提供的一些隐含证据来解决这个问题,

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
                                 ^
于 2013-07-27T15:22:52.950 回答
2

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

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
于 2012-11-17T15:53:00.953 回答
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然而,不能用它自己做那个把戏。可能值得提交增强请求。

于 2012-11-16T22:32:46.100 回答