WeiChing Lin WeiChing Lin - 3 months ago 8
Scala Question

Scala case class prohibits call-by-name parameters?

I want to implement an infinite list:

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


the problem is the
call-by-name
is not allowed.

I've heard that it is because
val
or
var
constructor parameter is not allowed for
call-by-name
. For example:

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


But the contradiction is that the normal constructor parameter is still
val
, despite
private
. For example:

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





So the question :


  • Is the problem really about
    val
    or
    var
    ?


    • If that. Since the point for call-by-name is to defer computation, Why could not
      val
      or
      var
      computation(or initialization) be deferred?


  • How to get around the cass class to implement an infinite list?


Answer

There is no contradiction: class A(x: => Int) is equivalent to class A(private[this] val x: => Int) and not class A(private val x: => Int). private[this] marks a value instance-private, while a private-modifier without further specification allows accessing the value from any instance of that class.

Unfortunately, defining a case class A(private[this] val x: => Int) is not allowed either. I assume it is because case-classes need access to the constructor values of other instances, because they implement the equals method.

Nevertheless, you could implement the features that a case class would provide manually:

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)
}

To check this code, you could try:

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")
  }
}

Unfortunately, I do not know for sure why call-by-name val and var members are prohibited. However, there is at least one danger to it: Think about how case-classes implement toString; The toString-method of every constructor value is called. This could (and in this example would) lead to the values calling themselves infinitely. You can check this by adding t.toString to MyNode's toString-method.

Edit: After reading Chris Martin's comment: The implementation of equals will also pose a problem that is probably more severe than the implementation of toString (which is mostly used for debugging) and hashCode (which will only lead to higher collision rates if you can't take the parameter into account). You have to think carefully about how you would implement equals to be meaningfull.

Comments