Класс случая и линеаризация признаков
Предположим, что я хочу написать класс case Stepper
следующим образом:
case class Stepper(step: Int) {def apply(x: Int) = x + step}
Он поставляется с красивой реализацией toString
:
scala> Stepper(42).toString
res0: String = Stepper(42)
но это не действительно функция:
scala> Some(2) map Stepper(2)
<console>:10: error: type mismatch;
found : Stepper
required: Int => ?
Some(2) map Stepper(2)
Обходной путь заключается в реализации признака Function
...
case class Stepper(step: Int) extends (Int => Int) {def apply(x: Int) = x + step}
Но тогда я не могу получить бесплатную реализацию toString:
scala> Stepper(42).toString
res2: java.lang.String = <function1>
Тогда возникает вопрос: могу ли я иметь лучшее из этих двух миров? Есть ли решение, где у меня есть хорошая реализация toString
для бесплатного И реализация признака Function
. Другими словами, существует ли способ применения линеаризации таким образом, чтобы наконец был применен синтаксический сахар case class
?
Ответы
Ответ 1
Вопрос не связан с линеаризацией. В case-classes toString
- это метод, автоматически генерируемый компилятором тогда и только тогда, когда Any.toString
не переопределяется в конечном типе.
Однако ответ частично связан с линеаризацией - нам нужно переопределить Function1.toString
с помощью метода, который был бы сгенерирован компилятором, если бы не версия, введенная Function1
:
trait ProperName extends Product {
override lazy val toString = scala.runtime.ScalaRunTime._toString(this)
}
// now just mix in ProperName and... magic!
case class Stepper(step: Int) extends (Int => Int) with ProperName {
def apply(x:Int) = x+step
}
Тогда
println(Some(2) map Stepper(2))
println(Stepper(2))
Произведет
Some(4)
Stepper(2)
Обновление
Вот версия признака ProperName
, которая не полагается на недокументированный API-метод:
trait ProperName extends Product {
override lazy val toString = {
val caseFields = {
val arity = productArity
def fields(from: Int): List[Any] =
if (from == arity) List()
else productElement(from) :: fields(from + 1)
fields(0)
}
caseFields.mkString(productPrefix + "(", ",", ")")
}
}
Альтернативная реализация toString
получена из исходного кода для исходного метода _toString
scala.runtime.ScalaRunTime._toString
.
Обратите внимание, что эта альтернативная реализация по-прежнему основана на предположении, что класс case всегда расширяет черту Product
. Несмотря на то, что последнее верно, как из Scala 2.9.0, и является фактом, который известен и полагается некоторыми членами сообщества Scala, он официально не зарегистрирован как часть Scala Спецификация языка.
Ответ 2
РЕДАКТИРОВАТЬ: Как насчет переопределения toString?
case class Stepper(step: Int) extends (Int => Int) {
def apply(x: Int) = x + step
override def toString = "Stepper(" + step + ")"
}
Ответ 3
Вы можете использовать неявное преобразование, чтобы Stepper
рассматривалось как функция только при необходимости:
case class Stepper(step: Int) { def apply(x: Int) = x + step }
implicit def s2f(s: Stepper) = new Function[Int, Int] {
def apply(x: Int) = s.apply(x)
}
Теперь вы получаете класс case toString
при вызове Stepper(42).toString
, но Some(2) map Stepper(2)
также работает по желанию.
(Обратите внимание, что я был более подробным, чем необходимо выше, чтобы очистить механику. Вы также можете написать implicit def s2f(s: Stepper) = s.apply _
или любое количество других более сжатых формулировок).