2014-11-05 67 views
22

我想要實現的無限名單:斯卡拉案例類禁止按名稱參數?

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是不允許的。

我聽說這是因爲或var的構造函數參數不允許爲call-by-name。例如:

class A(val x: =>Int) 
//error: `val' parameters may not be call-by-name 

但矛盾的是,正常的構造函數的參數仍然是val,儘管private。例如:

class A(x: =>Int) 
// pass 

所以問題:

  • 是真正的問題有關valvar
    • 如果是這樣。由於按名稱調用是爲了推遲計算,爲什麼不能延遲計算(或初始化)valvar
  • 如何繞過cass類來實現無限列表?
+4

對於無限數據結構,案例類糖提供什麼價值? 'equals','hashCode','toString'不起作用。而且我不確定我對'unapply'的期望。 – 2014-11-05 04:54:11

回答

15

沒有矛盾: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-方法來檢查。

編輯:的equals的實施也將帶來一個問題,即可能比的toString的實現(主要用於調試)和hashCode(這隻會導致更高的更嚴重:閱讀克里斯·馬丁的評論後碰撞率如果你不能把這個參數考慮在內)。你必須仔細考慮如何實現equals是有意義的。

+2

我認爲你只要按照原來的方式離開'equals',但要注意它只能用於終止列表。這就是'流'的工作原理,對吧? Stream(1)== Stream(1)'爲'true',但Stream.from(1)== Stream.from(1)'不停止。 – 2014-11-05 05:18:13

+0

@ChrisMartin取決於如何使用列表(沒有周期,這很好),但我可能會實現一個更安全的'equals'方法。例如,可以將一組已經訪問過的'MyList'實例傳遞給專門的'equalsMyList(other:MyList [T],visited:Set [MyList [T]]) - 方法。然後專門的方法可以通過檢查'this'是否已經包含在'visited'中來檢查遞歸,並且在這種情況下返回true。墮胎標準可能需要檢查**對象身份**而不是常規的平等,否則我們可能會遇到下一個無盡的遞歸。 – 2014-11-05 05:31:26

4

我還沒有找到爲什麼在案例類中禁止使用名副其實的參數。我想解釋應該是相當複雜和複雜的。 但在他的書「Functional Programming in Scala」中Runar Bjarnason提供了一個很好的方法來處理這個障礙。他將「thunk」的概念與記憶一起使用。 這裏是流實現的例子:

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: _*)) 
} 
} 

正如你看到的,而不是常規的名稱參數的情況下類數據構造他們使用他們所稱的「咚」的零參數的函數() => T。然後爲了使用戶透明,他們在伴隨對象中聲明瞭一個聰明的構造函數,它允許你提供一個名稱參數並使它們被記憶。