Scala 案例分类与性状线性化

Scala 案例分类与性状线性化,scala,traits,case-class,Scala,Traits,Case Class,假设我想编写一个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 mism

假设我想编写一个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)
但是,我再也不能免费拥有一个好的toString实现了:

scala> Stepper(42).toString
res2: java.lang.String = <function1>
scala>Stepper(42).toString
res2:java.lang.String=

那么,问题是:我能拥有这两个世界中最好的吗?有没有一个解决方案,我可以免费使用nice
toString
实现和trait
函数的实现
。换言之,是否有一种方法可以应用线性化,从而使
case class
syntaxic sugar最终被应用?

编辑:覆盖toString怎么样

case class Stepper(step: Int) extends (Int => Int) {
  def apply(x: Int) = x + step
  override def toString = "Stepper(" + step + ")"
}

只有在必要时,才可以使用隐式转换将步进器作为函数处理:

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)
}
现在,当您调用
Stepper(42)时,您将得到case类的
toString
。toString
,但是
一些(2)map Stepper(2)
也可以根据需要工作


(请注意,为了保持机制的清晰,我在上文中比必要时更加详细。您还可以编写
隐式def s2f(s:Stepper)=s.apply
或任何其他更简洁的公式)。

问题与线性化无关。在case class
toString
中,当且仅当
Any.toString
在end类型中未被重写时,编译器才会自动生成一个方法

然而,答案部分与线性化有关-我们需要用编译器生成的方法覆盖
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
}
然后

将产生

更新

下面是一个不依赖于未记录API方法的
ProperName
trait版本:

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
实现是从原始
\u toString
方法的源代码派生而来的


请注意,此替代实现仍然基于这样的假设,即案例类始终扩展
Product
trait。尽管后者在Scala 2.9.0中仍然适用,并且是Scala社区的一些成员所知道和依赖的事实,但它并没有作为其一部分进行正式记录

是的,我知道。但我真的不想添加un apply(想象一下在DSL中)。这个想法是为了找到可以重用的东西,这就是为什么我没有去重写toString。是的,但是构建这个隐式比重写toString更痛苦;)@尼古拉斯:真的吗?40个字符。这似乎比使用未记录的运行时API“痛苦”要小得多。这不是字符数的问题,而是向类中添加一些“非干式”代码的问题。
ScalaRuntime
解决方案不是完美的,显然不是生产准备好的,而是朝着干燥的方向发展。顺便说一句,我最初把它比作重写toString(参见Tal Pressman的回答)@Nicolas:我认为你不可能在没有隐式的情况下编写一个非常令人满意的DSL-ish库,在这种情况下隐式方法实际上比继承更干净:你只需要
函数
就可以使你的语法漂亮,那么为什么要将它强制到类层次结构中呢?不,我的观点不是隐式的使用。您的解决方案的问题是,每当我希望我的类被视为函数时,我必须创建一个新的隐式函数。此外,addind
函数
traitz不仅是语法的,而且是语义的:我的类的实例是函数。最后,问题不是“我可以用覆盖、隐式或其他任何方式来实现它”而是“我可以得到case类引入的toString定义吗”。是的,它不是真正的线性化,但我没有找到任何其他合适的名称。这正是我所期待的,谢谢。@Nicolas我非常理解你,我发现自己常常很难准确地描述一个问题,因为我不知道发生了什么。@Vlad:我总是避免使用scala.runtime中没有在scala API文档中显示的
scala.runtime
。我同意这是一个聪明的解决方案,但考虑到有同样好的解决方案使用普通的Scala语言特性,您真的认为值得吗?其他解决方案需要在每种情况下反复重新编写逻辑。当然,Vlad解决方案不是“生产就绪”,而是很好的。这就是为什么,除非有人使用标准API提供如此优雅的东西,否则它是可以接受的@Nicolas我认为,
toString
作为一个“很好拥有”的特性包含在case类中,这有助于在REPL中进行原型设计/调试/处理。通常情况下,您不会依赖代码中类的名称等实现细节来影响应用程序的IO,因此在生产代码中,如果您需要这样的内容,您可能希望包含自己的字符串表示。
println(Some(2) map Stepper(2))
println(Stepper(2))
Some(4)
Stepper(2)
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 + "(", ",", ")")
  }
}