情况下类复制“方法”与超(case class copy 'method' with

2019-06-28 04:52发布

我想要做这样的事情:

sealed abstract class Base(val myparam:String)

case class Foo(override val myparam:String) extends Base(myparam)
case class Bar(override val myparam:String) extends Base(myparam)

def getIt( a:Base ) = a.copy(myparam="changed")

我不能,因为在GETIT的情况下,我没有告诉每个基地有一个“复制”方法的编译程序,但副本是不是真的要么,所以我不认为有一个特质或抽象方法的方法我可以把基地,使这项工作正常。 或者,有没有?

如果我尝试定义碱作为abstract class Base{ def copy(myparam:String):Base } ,则case class Foo(myparam:String) extends Base导致class Foo needs to be abstract, since method copy in class Base of type (myparam: String)Base is not defined

有一些其他的方式来告诉编译器,所有的Base班会在执行case类? 一些特质,意思是“有一个案例类的属性”?

我可以让基地成为一个案例类,但然后我得到的编译器警告说,从案例类,继承已被弃用?

我知道我还可以:

def getIt(f:Base)={ 
  (f.getClass.getConstructors.head).newInstance("yeah").asInstanceOf[Base]
}

但...这似乎非常难看。

思考? 是我的整个做法只是“错”?

UPDATE我改变了基类来包含的属性,进行的情况类使用“覆盖”关键字。 这能更好地反映实际问题,使问题更现实的考虑Edmondo1984的回应。

Answer 1:

这是旧的答案,这个问题被改变了。

强类型的编程语言阻止你正在尝试做的。 让我们来看看为什么。

具有以下签名的方法的想法:

def getIt( a:Base ) : Unit

是该方法的主体将能够通过基类或接口来访问可见性,即只能在基类/接口或者其父母所定义的属性和方法。 在代码执行期间,传递到每一个具体的实例getIt方法可能有不同的子类,但的编译型a将永远是Base

我们可以推理过程是这样:

好吧,我有一个Base类,我继承了它在两个案例类和我添加属性具有相同的名称,然后我试图访问基地的实例的属性。

一个简单的例子说明了为什么这是不安全的:

sealed abstract class Base
case class Foo(myparam:String) extends Base
case class Bar(myparam:String) extends Base
case class Evil(myEvilParam:String) extends Base

def getIt( a:Base ) = a.copy(myparam="changed")

在以下情况下,如果编译器不能在编译的时候抛出一个错误,这意味着代码将尝试访问在运行时不存在的属性。 这是不可能在严格类型的编程语言:你上,你可以为你的代码编译器的一个更强大的验证编写的代码上市交易的限制,因为他们知道这将大大降低你的代码可以包含bug的数量


这是新的答案。 因为得到的结论之前需要几个点这是一个有点长

不幸的,你可以不依赖于案件的类别复制到实现你提出什么机制。 复制方法的工作方式是简单的拷贝构造函数,你可以实现自己在一个不区分类。 让我们创建一个case类,并在REPL拆解:

scala>  case class MyClass(name:String, surname:String, myJob:String)
defined class MyClass

scala>  :javap MyClass
Compiled from "<console>"
public class MyClass extends java.lang.Object implements scala.ScalaObject,scala.Product,scala.Serializable{
    public scala.collection.Iterator productIterator();
    public scala.collection.Iterator productElements();
    public java.lang.String name();
    public java.lang.String surname();
    public java.lang.String myJob();
    public MyClass copy(java.lang.String, java.lang.String, java.lang.String);
    public java.lang.String copy$default$3();
    public java.lang.String copy$default$2();
    public java.lang.String copy$default$1();
    public int hashCode();
    public java.lang.String toString();
    public boolean equals(java.lang.Object);
    public java.lang.String productPrefix();
    public int productArity();
    public java.lang.Object productElement(int);
    public boolean canEqual(java.lang.Object);
    public MyClass(java.lang.String, java.lang.String, java.lang.String);
}

在Scala中,拷贝方法有三个参数,并最终使用来自你没有指定一个当前实例的一个(Scala语言其功能的默认值中提供的参数在方法调用)

让我们下去在我们的分析和更新,再取代码:

sealed abstract class Base(val myparam:String)

case class Foo(override val myparam:String) extends Base(myparam)
case class Bar(override val myparam:String) extends Base(myparam)

def getIt( a:Base ) = a.copy(myparam="changed")

现在,为了使这个编译,我们将需要的签名使用getIt(a:MyType)一个MyType尊重以下合同:

凡是有一个参数myparam并有默认值也许其他参数

所有这些方法都将是合适的:

  def copy(myParam:String) = null
  def copy(myParam:String, myParam2:String="hello") = null
  def copy(myParam:String,myParam2:Option[Option[Option[Double]]]=None) = null

有没有办法来表达斯卡拉合同,但有先进的技术,可以帮助。

我们能够做的第一观察是,有之间有严格的关系case classestuples在Scala中。 事实上case类是具有附加行为和命名属性莫名其妙元组。

第二个观察是,因为你的类层次结构的属性的数量不能保证是一样的, 复制方法的签名是不能保证是相同的。

在实践中,假设AnyTuple[Int]描述的任何Tuple的任何尺寸,其中第一值是int类型的,我们正在做这样的事情:

def copyTupleChangingFirstElement(myParam:AnyTuple[Int], newValue:Int) = myParam.copy(_1=newValue)

这不会是困难的,如果所有的元素都Int 。 与同类型的所有元素的元组是一个List ,我们知道如何更换的第一个元素List 。 我们需要任何转换TupleXList ,更换的第一个元素,并转换ListTupleX 。 是的,我们将需要编写所有的转换器的所有值X可能承担的风险。 恼人的,但并不难。

在我们的例子中,并不是所有的元素都Int 。 我们要正确对待Tuple在那里,好像他们都是相同的,如果第一个元素是一个int元素是不同的类型。 这就是所谓的

“在抽象元数”

即治疗一种通用的方式不同大小的元组,独立于它们的大小。 要做到这一点,我们需要把它们转化成支持异构类型的一个特殊列表,命名HList


结论

案例类继承已经废弃了很好的理由,你可以从邮件列表多个职位找到: http://www.scala-lang.org/node/3289

你有两个战略来处理你的问题:

  1. 如果您有需要改变字段的数量有限,使用的方法,如一个接@Ron建议,这是具有复制方法。 如果你想这样做,而不会丢失类型信息,我会去的泛型化基类

     sealed abstract class Base[T](val param:String){ def copy(param:String):T } class Foo(param:String) extends Base[Foo](param){ def copy(param: String) = new Foo(param) } def getIt[T](a:Base[T]) : T = a.copy("hello") scala> new Foo("Pippo") res0: Foo = Foo@4ab8fba5 scala> getIt(res0) res1: Foo = Foo@5b927504 scala> res1.param res2: String = hello 
  2. 如果你真的想在抽象元数,一个解决方案是使用由Miles萨宾开发的库称为无形。 目前已提出的讨论后,这里的一个问题: 是否HLists无非就是写一个元组的令人费解的方式吗? 但我告诉你,这是要给你一些头痛



Answer 2:

如果这两个班的情况下会随着时间的推移分歧,使他们有不同的字段,然后共享copy的方式将停止工作。

这是更好地定义一个抽象def withMyParam(newParam: X): Base 。 更妙的是,你可以引入一个抽象类型在返回保留的情况下类类型:

scala> trait T {
     |   type Sub <: T
     |   def myParam: String
     |   def withMyParam(newParam: String): Sub
     | }
defined trait T

scala> case class Foo(myParam: String) extends T {
     |   type Sub = Foo
     |   override def withMyParam(newParam: String) = this.copy(myParam = newParam)
     | }
defined class Foo

scala>

scala> case class Bar(myParam: String) extends T {
     |   type Sub = Bar
     |   override def withMyParam(newParam: String) = this.copy(myParam = newParam)
     | }
defined class Bar

scala> Bar("hello").withMyParam("dolly")
res0: Bar = Bar(dolly)


Answer 3:

TL; DR:我设法声明复制方法的基础,同时还让编译器自动生成在派生类的情况下,它的实现。 这涉及到一个小伎俩(其实我倒是自己刚刚重新设计的类型层次结构),但它至少表明一点,你的确可以让它没有任何派生类的情况下写作锅炉板代码工作。

首先,如已经由Ron和Edmondo1984提到的,你会陷入麻烦,如果你的情况类有不同的领域。

我将严格坚持自己的例子,虽然,假设你的所有情况下的类具有相同的字段(看你的github上的链接,这似乎是你的实际代码的情况下也是如此)。

考虑到所有情况下的类具有相同的字段,自动生成的copy方法将是一个良好的开端相同的签名。 这似乎是合理的,然后到刚才添加的共同定义在Base ,像你一样: abstract class Base{ def copy(myparam: String):Base }问题是现在斯卡拉不会产生copy方法,因为已经有一个在基类。

事实证明,还有另一种方式来静态地确保Base有权copy方法,它是通过结构类型和自我类型的注释:

type Copyable = { def copy(myParam: String): Base }
sealed abstract class Base(val myParam: String) { this : Copyable => }

而不像我们早期的尝试,这不会阻止斯卡拉自动生成的copy方法。 最后还有一个问题:自己类型的注释可以确保的子类Base有一个copy的方法,但它不让它上公开速效Base

val foo: Base = Foo("hello")
foo.copy()
scala> error: value copy is not a member of Base

要解决这一点,我们可以从基地添加隐式转换到可复制。 一个简单的投会做,作为一个基本保证是能够复制的:

implicit def toCopyable( base: Base ): Base with Copyable = base.asInstanceOf[Base with Copyable]

结束了,这给了我们:

object Base {
  type Copyable = { def copy(myParam: String): Base }
  implicit def toCopyable( base: Base ): Base with Copyable = base.asInstanceOf[Base with Copyable]
}
sealed abstract class Base(val myParam: String) { this : Base. Copyable => }

case class Foo(override val myParam: String) extends Base( myParam )
case class Bar(override val myParam: String) extends Base( myParam )

def getIt( a:Base ) = a.copy(myParam="changed")

红利效应:如果我们试图定义具有不同的签名的情况下类,我们得到一个编译错误:

case class Baz(override val myParam: String, truc: Int) extends Base( myParam ) 
scala> error: illegal inheritance; self-type Baz does not conform to Base's selftype Base with Base.Copyable

要完成,一个警告:你应该只是修改你的设计,以避免诉诸上述伎俩。 在你的情况,罗恩的使用单例类有一个额外的建议etype现场似乎比更合理。



Answer 4:

我认为这是扩展方法是。 把你的执行战略挑的复制方法本身。

我喜欢这里的问题是在一个地方解决。

有趣的是问为什么没有的特质对caseness:它不会说太多关于如何调用拷贝,但它可以始终没有ARGS被调用, copy()

sealed trait Base { def p1: String }

case class Foo(val p1: String) extends Base
case class Bar(val p1: String, p2: String) extends Base
case class Rab(val p2: String, p1: String) extends Base
case class Baz(val p1: String)(val p3: String = p1.reverse) extends Base

object CopyCase extends App {

  implicit class Copy(val b: Base) extends AnyVal {
    def copy(p1: String): Base = b match {
      case foo: Foo => foo.copy(p1 = p1)
      case bar: Bar => bar.copy(p1 = p1)
      case rab: Rab => rab.copy(p1 = p1)
      case baz: Baz => baz.copy(p1 = p1)(p1.reverse)
    }
    //def copy(p1: String): Base = reflect invoke
    //def copy(p1: String): Base = macro xcopy
  }

  val f = Foo("param1")
  val g = f.copy(p1="param2") // normal
  val h: Base = Bar("A", "B")
  val j = h.copy("basic")     // enhanced
  println(List(f,g,h,j) mkString ", ")

  val bs = List(Foo("param1"), Bar("A","B"), Rab("A","B"), Baz("param3")())
  val vs = bs map (b => b copy (p1 = b.p1 * 2))
  println(vs)
}

只是为了好玩,反射副本:

  // finger exercise in the api
  def copy(p1: String): Base = {
    import scala.reflect.runtime.{ currentMirror => cm }
    import scala.reflect.runtime.universe._
    val im = cm.reflect(b)
    val ts = im.symbol.typeSignature
    val copySym = ts.member(newTermName("copy")).asMethod
    def element(p: Symbol): Any = (im reflectMethod ts.member(p.name).asMethod)()
    val args = for (ps <- copySym.params; p <- ps) yield {
      if (p.name.toString == "p1") p1 else element(p)
    }
    (im reflectMethod copySym)(args: _*).asInstanceOf[Base]
  }


Answer 5:

还有就是如何做到这一点使用不成形在一个非常全面的解释http://www.cakesolutions.net/teamblogs/copying-sealed-trait-instances-a-journey-through-generic-programming-and-shapeless ; 在情况下,链接断裂,方法使用从无形的copySyntax事业,这应该足以找到更多的细节。



Answer 6:

这对我来说工作得很好:

sealed abstract class Base { def copy(myparam: String): Base }

case class Foo(myparam:String) extends Base {
  override def copy(x: String = myparam) = Foo(x)
}

def copyBase(x: Base) = x.copy("changed")

copyBase(Foo("abc")) //Foo(changed)


Answer 7:

一个老问题,与旧的解决方案,

https://code.google.com/p/scala-scales/wiki/VirtualConstructorPreSIP

进行的情况类复制方法出现之前。

因此,在参考这一问题的各种情况下类必须是无论如何叶节点,所以定义复制和MyType的/ thisType加上newThis功能和您设置,每种情况下的类固定型。 如果你想扩展树/ newThis功能,并使用默认参数,你就必须更改名称。

顺便说一句 - 我一直在等待编译器插件魔法实现这一点,但类型宏可能是魔术般的汁液前改善。 凯文的自动代理的列表搜索为什么我的代码哪儿也不去了更详细的解释



文章来源: case class copy 'method' with superclass