簡體   English   中英

Scala案例類禁止按名稱調用參數?

[英]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不允許valvar構造函數參數。 例如:

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是推遲計算。 為什么不能推遲valvar計算(或初始化)?
  • 如何繞過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添加到MyNodetoString方法來檢查這一點。

編輯:閱讀 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM