Подтвердить что ты не робот

Scala класс case запрещает параметры вызова по имени?

Я хочу реализовать бесконечный список:

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 не допускается.

Я слышал, что это связано с тем, что параметр конструктора val или 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

Итак, вопрос:

  • Действительно ли проблема о val или var?
    • Если это. Поскольку точкой для вызова по имени является отсрочка вычисления, почему не может быть отложено вычисление (или инициализация) val или var?
  • Как обойти класс cass для реализации бесконечного списка?
4b9b3361

Ответ 1

Нет противоречия: class A(x: => Int) эквивалентно class A(private[this] val x: => Int), а не class A(private val x: => Int). private[this] обозначает значение instance-private, а private-модификатор без дополнительной спецификации позволяет получить доступ к значению из любого экземпляра этого класса.

К сожалению, определение a case class A(private[this] val x: => Int) также не допускается. Я предполагаю, что это потому, что case-классы нуждаются в доступе к значениям конструктора других экземпляров, потому что они реализуют метод equals.

Тем не менее вы можете реализовать функции, которые класс case предоставит вручную:

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 toString; Вызывается toString -метод каждого значения конструктора. Это могло бы (и в этом примере) привести к значениям, называющим себя бесконечно. Вы можете проверить это, добавив t.toString в MyNode toString -метод.

Изменить: После прочтения комментария Криса Мартина: реализация equals также будет представлять проблему, которая, вероятно, более серьезная, чем реализация toString (которая в основном используется для отладки) и hashCode (что приведет только к более высокой скорости столкновений, если вы не можете принять этот параметр). Вы должны тщательно подумать о том, как реализовать equals, чтобы иметь смысл.

Ответ 2

Я также не нашел, почему именно запрещенные параметры запрещены в классах классов. Думаю, объяснение должно быть довольно сложным и сложным. Но Рунар Бьярнасон в своей книге "" Функциональное программирование в Scala "обеспечивает хороший подход к устранению этого препятствия. Он использует концепцию" мошенничества" вместе с воспоминаниями. Ниже приведен пример реализации 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: _*))
 }
}

Как вы видите, вместо обычного параметра by-name для конструктора данных класса case они используют то, что они называют "thunk", функцией нулевых аргументов () => T. Затем, чтобы сделать это прозрачным для пользователя, они объявляют интеллектуальный конструктор в сопутствующем объекте, который позволяет вам предоставить параметры имени и сделать их memoized.