2014-11-05 61 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。然后为了使用户透明,他们在伴随对象中声明了一个聪明的构造函数,它允许你提供一个名称参数并使它们被记忆。