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

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

问题描述

我想做这样的事情:

密封抽象类 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 都有一个复制"方法,但复制也不是真正的方法,所以我认为没有特征或抽象方法,我可以放在 Base 中以使其正常工作.或者,有吗?

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

是否有其他方法可以告诉编译器所有 Base 类在它们的实现中都是 case 类?某些特征意味着具有案例类的属性"?

我可以让 Base 成为一个案例类,但是我收到编译器警告说不推荐使用案例类的继承?

我知道我也可以:

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

但是……这看起来很丑.

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

UPDATE 我更改了基类以包含属性,并使案例类使用覆盖"关键字.考虑到Edmondo1984的回应,这更好地反映了实际问题,使问题更加现实.

解决方案

这是旧答案,在问题改变之前.

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

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

def getIt( a:Base ) : 单位

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

可以这样推理:

<块引用>

好的,我有一个 Base 类,我在两个 case 类中继承了它,并添加了一个具有相同名称的属性,然后我尝试访问该属性Base 的实例.

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

密封抽象类Basecase class Foo(myparam:String) extends Basecase class Bar(myparam:String) extends Basecase class Evil(myEvilParam:String) extends Basedef getIt( a:Base ) = a.copy(myparam="changed")

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

<小时>

这是新的答案.有点长,因为在得出结论之前需要几点

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

scala>case class MyClass(name:String, surname:String, myJob:String)定义类 MyClass标度>:javap 我的类编译自<控制台>"公共类 MyClass 扩展 java.lang.Object 实现 scala.ScalaObject,scala.Product,scala.Serializable{公共 scala.collection.Iterator productIterator();公共 scala.collection.Iterator productElements();public java.lang.String name();public java.lang.String surname();公共 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();公共 int hashCode();public java.lang.String toString();公共布尔等于(java.lang.Object);public java.lang.String productPrefix();public int productArity();public java.lang.Object productElement(int);公共布尔 canEqual(java.lang.Object);public MyClass(java.lang.String, java.lang.String, java.lang.String);}

在 Scala 中,copy 方法接受三个参数,最终可以将当前实例中的一个用于您尚未指定的那个(Scala 语言在其功能中为方法调用中的参数提供了默认值)

让我们继续分析并再次查看更新后的代码:

密封抽象类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) = nulldef copy(myParam:String, myParam2:String="hello") = nulldef copy(myParam:String,myParam2:Option[Option[Option[Double]]]=None) = null

在 Scala 中没有办法表达这个契约,但是有一些先进的技术可以提供帮助.

我们可以做的第一个观察是在 Scala 中 case classestuples 之间存在严格的关系.事实上,case 类是以某种方式具有附加行为和命名属性的元组.

第二个观察是,由于不能保证您的类层次结构的属性数量相同,因此不能保证 copy 方法 signature 是相同的.

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

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

如果所有元素都是Int,这不会太困难.所有元素都相同类型的元组是一个List,我们知道如何替换List的第一个元素.我们需要将任何 TupleX 转换为 List,替换第一个元素,然后将 List 转换回 TupleX.是的,我们需要为 X 可能假设的所有值编写所有转换器.烦人但不难.

但在我们的例子中,并非所有元素都是 Int.我们希望将 Tuple 中的元素类型视为不同类型,如果第一个元素是 Int,则它们都相同.这叫做

<块引用>

抽象过度"

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

<小时>

结论

案例类继承被弃用是有充分理由的,你可以从邮件列表中的多个帖子中找到:http://www.scala-lang.org/node/3289

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

  1. 如果您需要更改的字段数量有限,请使用@Ron 建议的方法,即具有复制方法的方法.如果你想在不丢失类型信息的情况下做到这一点,我会去泛化基类

    密封抽象类 Base[T](val param:String){定义复制(参数:字符串):T}类 Foo(param:String) 扩展 Base[Foo](param){def copy(param: String) = new Foo(param)}def getIt[T](a:Base[T]) : T = a.copy("hello")标度>new Foo("Pippo")res0: Foo = Foo@4ab8fba5标度>得到它(res0)res1:Foo = Foo@5b927504标度>res1.paramres2:字符串 = 你好

  2. 如果您真的想对元数进行抽象,一个解决方案是使用由 Miles Sabin 开发的名为 Shapeless 的库.这里有一个问题,经过讨论后被问到:HLists 只是一种编写元组的复杂方式吗? 但我告诉你,这会让你头疼

I want to do something like this:

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

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?

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

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

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

I know I can also:

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

but... that seems very ugly.

Thoughts? Is my whole approach just "wrong" ?

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.

解决方案

This is old answer, before the question was changed.

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

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

One can reason in this way:

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

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);
}

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

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:

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

All these methods would be suitable:

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

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

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.

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)

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.

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"

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


Conclusion

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. 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
    

  2. 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

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

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