Scala 案例分类与性状线性化
假设我想编写一个case类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
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 classtoString
中,当且仅当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 + "(", ",", ")")
}
}