case类复制'方法'与超类 [英] case class copy 'method' with superclass

查看:128
本文介绍了case类复制'方法'与超类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想这样做:

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的上下文中,我没有告诉编译器每个Base都有一个'copy'方法,但是副本不是一个真正的方法,所以我不认为有一个trait或抽象方法我可以放在Base中使其正常工作。

I can't, because in the context of getIt, I haven't told the compiler that every Base has a 'copy' method, but copy isn't really a method either so I don't think there's a trait or abstract method I can put in Base to make this work properly. Or, is there?

如果我尝试定义Base为抽象类Base {def copy(myparam:String):Base} case类Foo(myparam:String)extends Base 结果在类Foo需要是抽象的,因为方法复制在类Base类型(myparam:String)Base未定义

If I try to define Base as abstract class Base{ def copy(myparam:String):Base }, then case class Foo(myparam:String) extends Base results in class Foo needs to be abstract, since method copy in class Base of type (myparam: String)Base is not defined

是否有其他方法告诉编译器所有 Base 类将在它们的实现中的case类?一些trait意味着具有case类的属性?

Is there some other way to tell the compiler that all Base classes will be case classes in their implementation? Some trait that means "has the properties of a case class"?

我可以使Base是一个case类,但是后来我得到编译器警告说从case类继承已被弃用?

I could make Base be a case class, but then I get compiler warnings saying that inheritance from case classes is deprecated?

我知道我也可以:

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

但这似乎很丑陋。

想法?我的整个方法只是错误?

Thoughts? Is my whole approach just "wrong" ?

UPDATE 我改变了基类以包含属性,覆盖关键字。

UPDATE I changed the base class to contain the attribute, and made the case classes use the "override" keyword. This better reflects the actual problem and makes the problem more realistic in consideration of Edmondo1984's response.

推荐答案

这是老的答案

强类型的编程语言会阻止您尝试执行的操作。让我们来看看为什么。

Strongly typed programming languages prevent what you are trying to do. Let's see why.

使用以下签名的方法的想法:

The idea of a method with the following signature:

def getIt( a:Base ) : Unit

能够访问通过Base类或接口可见的属性,即仅在Base类/接口或其父类上定义的属性和方法。在代码执行期间,传递给 getIt 方法的每个特定实例可能具有不同的子类,但是 a 的编译类型将总是基本

Is that the body of the method will be able to access a properties visible through Base class or interface, i.e. the properties and methods defined only on the Base class/interface or its parents. During code execution, each specific instance passed to the getIt method might have a different subclass but the compile type of a will always be Base

可以这样理解:


Ok我有一个类Base,我继承它在两个case类和我添加一个
属性具有相同的名称,然后我尝试访问的属性
Base的实例。

Ok I have a class Base, I inherit it in two case classes and I add a property with the same name, and then I try to access the property on the instance of Base.

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

A simple example shows why this is unsafe:

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")

在下面的情况下,如果编译器在编译时没有抛出错误,这意味着代码将尝试访问在运行时不存在的属性。这在严格类型的编程语言中是不可能的:您已经对编码的代码进行交易限制,以便由编译器进行更强大的代码验证,因为知道这大大减少了代码可以包含的错误数量

In the following case, if the compiler didn't throw an error at compile time, it means the code would try to access a property that does not exist at runtime. This is not possible in strictly typed programming languages: you have traded restrictions on the code you can write for a much stronger verification of your code by the compiler, knowing that this reduces dramatically the number of bugs your code can contain

这是新的答案。

This is the new answer. It is a little long because few points are needed before getting to the conclusion

不幸的是,你不能依赖case类的机制复制以实现您的建议。复制方法的工作方式只是一个复制构造函数,您可以在非case类中实现自己。让我们创建一个case类并在REPL中反汇编:

Unluckily, you can't rely on the mechanism of case classes copy to implement what you propose. The way the copy method works is simply a copy constructor which you can implement yourself in a non-case class. Let's create a case class and disassemble it in the 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语言在其功能中提供方法调用中的参数的默认值)

In Scala, the copy method takes three parameter and can eventually use the one from the current instance for the one you haven't specified ( the Scala language provides among its features default values for parameters in method calls)

让我们进入我们的分析,再次更新代码:

Let's go down in our analysis and take again the code as updated:

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) a MyType 时遵守以下合同:

Now in order to make this compile, we would need to use in the signature of getIt(a:MyType) a MyType that respect the following contract:


任何具有参数myparam和其他参数,
都有默认值

Anything that has a parameter myparam and maybe other parameters which have default value

这些方法都是合适的:

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

没有办法在Scala中表达此合同,但是有高级技术可以帮助。

There is no way to express this contract in Scala, however there are advanced techniques that can be helpful.

我们可以做的第一个观察是,案例类和<$ c $之间有严格的关系c> tuples 。事实上,case类是以某种方式具有额外行为和命名属性的元组。

The first observation that we can do is that there is a strict relation between case classes and tuples in Scala. In fact case classes are somehow tuples with additional behaviour and named properties.

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

The second observation is that, since the number of properties of your classes hierarchy is not guaranteed to be the same, the copy method signature is not guaranteed to be the same.

实际上,假设 AnyTuple [Int] 描述任何 Tuple 任何大小,其中第一个值是Int类型,我们正在寻找做这样的事情:

In practice, supposing AnyTuple[Int] describes any Tuple of any size where the first value is of type Int, we are looking to do something like that:

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

如果所有元素 Int ,则不会困难。具有相同类型的所有元素的元组是 List ,我们知道如何替换 List 。我们需要将 TupleX 转换为 List ,替换第一个元素,并将列表返回 TupleX 。是的,我们需要为 X 可能假设的所有值写入所有转换器。恼人但不困难。

This would not be to difficult if all the elements were Int. A tuple with all element of the same type is a List, and we know how to replace the first element of a List. We would need to convert any TupleX to List, replace the first element, and convert the List back to TupleX. Yes we will need to write all the converters for all the values that X might assume. Annoying but not difficult.

在我们的例子中,并不是所有的元素都是 Int 。我们想要处理 Tuple ,其中元素是不同的类型,如果第一个元素是一个Int,则它们都是相同的。这被称为

In our case though, not all the elements are Int. We want to treat Tuple where the elements are of different type as if they were all the same if the first element is an Int. This is called


抽象化过程

"Abstracting over arity"

ie以通用方式处理不同大小的元组,而与其大小无关。为此,我们需要将它们转换为支持异类类型的特殊列表,命名为 HList

i.e. treating tuples of different size in a generic way, independently of their size. To do it, we need to convert them into a special list which supports heterogenous types, named HList

结论

案例类继承被弃用的原因很多,你可以从多个帖子中发现在邮件列表中: http://www.scala-lang.org/node/3289

Case classes inheritance is deprecated for very good reason, as you can find out from multiple posts in the mailing list: http://www.scala-lang.org/node/3289

您有两种策略来处理您的问题:

You have two strategies to deal with your problem:


  1. 有限数量的字段需要更改,使用一种方法,例如由@Ron建议,这是一个复制方法。如果你想做而不丢失类型信息,我会去生成基类

  1. If you have a limited number of fields you require to change, use an approach such as the one suggested by @Ron, which is having a copy method. If you want to do it without losing type information, I would go for generifying the base class

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


  • 如果您真的想抽象一个解决方案是使用由Miles Sabin开发的一个名为Shapeless的库。这里有一个问题,在讨论后被问过:

  • If you really want to abstract over arity, a solution is to use a library developed by Miles Sabin called Shapeless. There is a question here which has been asked after a discussion : Are HLists nothing more than a convoluted way of writing tuples? but I tell you this is going to give you some headache

    这篇关于case类复制'方法'与超类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆