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

Kan*_*ane 6 scala variadic-functions shapeless

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

def apply(i: Int*): Double = ...
Run Code Online (Sandbox Code Playgroud)

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

def set(v: Double, i: Int*) ...
def add(v: Double, i: Int*) ...
Run Code Online (Sandbox Code Playgroud)

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

  1. 在更新函数中更改参数的顺序,这使它看起来很奇怪.
  2. 允许可变长度参数不是最后一个.我发现这个问题在一般设置中提出,可以通过使用currying函数来解决,这里不适用.

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

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

Mil*_*bin 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)
Run Code Online (Sandbox Code Playgroud)

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

scala> i(23, true, 'c') = "foo"
res4: (Int, Boolean, Char, String) = (23,true,c,foo)
Run Code Online (Sandbox Code Playgroud)

但我们可以通过在无形 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
                                 ^
Run Code Online (Sandbox Code Playgroud)


Rég*_*les 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 ...
}
Run Code Online (Sandbox Code Playgroud)

用法:

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)
  }
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)