[英]Scala case class prohibits call-by-name parameters?
設想:
我想實現一個無限列表:
abstract class MyList[+T]
case object MyNil extends MyList[Nothing]
case class MyNode[T](h:T,t: => MyList[T]) extends MyList[T]
//error: `val' parameters may not be call-by-name
問題:
錯誤是不允許call-by-name
。
我聽說這是因為call-by-name
不允許val
或var
構造函數參數。 例如:
class A(val x: =>Int)
//error: `val' parameters may not be call-by-name
但相比之下,普通的構造函數參數仍然是val
,盡管是private
。 例如:
class A(x: =>Int)
// pass
所以問題:
val
還是var
?
call-by-name
是推遲計算。 為什么不能推遲val
或var
計算(或初始化)?case class
限制來實現無限列表?沒有矛盾: class A(x: => Int)
等價於class A(private[this] val x: => Int)
而不是class A(private val x: => Int)
。 private[this]
標記一個值 instance-private,而沒有進一步說明的 private-modifier 允許從該類的任何實例訪問該值。
不幸的是,也不允許定義case class A(private[this] val x: => Int)
。 我認為這是因為 case-classes 需要訪問其他實例的構造函數值,因為它們實現了equals
方法。
不過,您可以手動實現案例類將提供的功能:
abstract class MyList[+T]
class MyNode[T](val h: T, t: => MyList[T]) extends MyList[T]{
def getT = t // we need to be able to access t
/* EDIT: Actually, this will also lead to an infinite recursion
override def equals(other: Any): Boolean = other match{
case MyNode(i, y) if (getT == y) && (h == i) => true
case _ => false
}*/
override def hashCode = h.hashCode
override def toString = "MyNode[" + h + "]"
}
object MyNode {
def apply[T](h: T, t: => MyList[T]) = new MyNode(h, t)
def unapply[T](n: MyNode[T]) = Some(n.h -> n.getT)
}
要檢查此代碼,您可以嘗試:
def main(args: Array[String]): Unit = {
lazy val first: MyNode[String] = MyNode("hello", second)
lazy val second: MyNode[String] = MyNode("world", first)
println(first)
println(second)
first match {
case MyNode("hello", s) => println("the second node is " + s)
case _ => println("false")
}
}
不幸的是,我不確定為什么禁止按名稱調用 val 和 var 成員。 然而,它至少存在一個危險:想想 case-classes 如何實現toString
; 調用每個構造函數值的toString
方法。 這可能(並且在本示例中)導致值無限地調用自己。 您可以通過將t.toString
添加到MyNode
的toString
方法來檢查這一點。
編輯:閱讀 Chris Martin 的評論后: equals
的實現也會帶來一個問題,該問題可能比toString
(主要用於調試)和hashCode
(如果可以的話,只會導致更高的沖突率)的實現更嚴重t 考慮參數)。 您必須仔細考慮如何實現equals
才能有意義。
我也沒有發現為什么在 case 類中完全禁止按名稱參數。 我想解釋應該非常詳盡和復雜。 但是 Runar Bjarnason 在他的書“ Functional Programming in Scala ”中提供了一個很好的方法來處理這個障礙。 他將“thunk”的概念與記憶結合使用。 下面是一個Stream實現的例子:
sealed trait Stream[+A]
case object Empty extends Stream[Nothing]
case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]
object Stream {
def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = {
lazy val head = hd
lazy val tail = tl
Cons(() => head, () => tail)
}
def empty[A]: Stream[A] = Empty
def apply[A](as: A*): Stream[A] =
if (as.isEmpty) empty else cons(as.head, apply(as.tail: _*))
}
}
如您所見,案例類數據構造函數沒有使用常規的按名稱參數,而是使用他們所謂的“thunk”,即零參數() => T
的函數。 然后為了使用戶透明,他們在伴生對象中聲明了一個智能構造函數,它允許您提供按名稱參數並使它們記憶化。
這實際上與Stream
解決方案類似,但簡化為實際需要的方法:
case class A(x: () => Int) {
lazy val xx = x()
}
因此,您可以將案例類用作:
def heavyOperation: Int = ???
val myA = A(heavyOperation)
val myOtherA = A(() => 10)
val useA = myA.xx + myOtherA.xx
像這樣,只有在使用xx
時才會執行實際的繁重操作,即僅在最后一行。
我喜歡使用隱式函數使 thunk 像按名稱調用一樣工作。
例如在這個例子中:
case class Timed[R](protected val block: () => R) {
override def toString() = s"Elapsed time: $elapsedTime"
val t0 = System.nanoTime()
val result = block() // execute thunk
val t1 = System.nanoTime()
val elapsedTime = t1 - t0
}
implicit def blockToThunk[R](bl: => R) = () => bl //helps to call Timed without the thunk syntax
這讓你用按名稱語法調用 Timed({Thread.sleep(1000); println("hello")}) 例如
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.