nai*_*rbv 26 inheritance enumeration scala class
我想做这样的事情:
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")
Run Code Online (Sandbox Code Playgroud)
我不能,因为在getIt的上下文中,我没有告诉编译器每个Base都有一个'copy'方法,但copy也不是一个真正的方法所以我不认为有特征或抽象方法我可以放入Base以使其正常工作.或者,有吗?
如果我尝试将Base定义为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类?一些特征意味着"具有案例类的属性"?
我可以让Base成为一个案例类,但后来我得到编译器警告,说不推荐使用case类继承吗?
我知道我也可以:
def getIt(f:Base)={
(f.getClass.getConstructors.head).newInstance("yeah").asInstanceOf[Base]
}
Run Code Online (Sandbox Code Playgroud)
但是......这看起来很难看.
思考?我的整个方法是"错误的"吗?
更新我更改了基类以包含属性,并使案例类使用"override"关键字.这更好地反映了实际问题,并考虑到Edmondo1984的响应使问题更加真实.
Edm*_*984 24
在问题发生变化之前,这是旧答案.
强类型编程语言会阻止您尝试执行的操作.让我们看看为什么.
具有以下签名的方法的想法:
def getIt( a:Base ) : Unit
Run Code Online (Sandbox Code Playgroud)
是否该方法的主体将能够访问通过Base类或接口可见的属性,即仅在Base类/接口或其父级上定义的属性和方法.在代码执行期间,传递给getIt方法的每个特定实例可能具有不同的子类,但编译类型a始终为Base
可以用这种方式推理:
好吧我有一个类Base,我在两个case类中继承它,我添加一个具有相同名称的属性,然后我尝试访问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")
Run Code Online (Sandbox Code Playgroud)
在下面的例子中,如果编译器在编译时没有抛出错误,则意味着代码将尝试访问在运行时不存在的属性.这在严格类型化的编程语言中是不可能的:您已经对编写的代码进行了交易限制,以便编译器对代码进行更强大的验证,因为它知道这会大大减少代码可能包含的错误数量
这是新的答案.这有点长,因为在得出结论之前需要几点
不幸的是,您不能依赖案例类复制的机制来实现您的建议.复制方法的工作方式只是一个复制构造函数,您可以在非案例类中实现它.让我们创建一个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);
}
Run Code Online (Sandbox Code Playgroud)
在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")
Run Code Online (Sandbox Code Playgroud)
现在,为了使这个编译,我们将需要的签名使用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
Run Code Online (Sandbox Code Playgroud)
在Scala中无法表达此合同,但是有一些先进的技术可以提供帮助.
我们能够做的第一观察是,有一间严格关系case classes和tuplesScala中.事实上,case类是以某种方式具有附加行为和命名属性的元组.
第二个观察是,由于类层次结构的属性数量不保证相同,因此不保证复制方法签名是相同的.
在实践中,假设AnyTuple[Int]描述任何Tuple大小,其中第一个值是Int类型,我们希望做类似的事情:
def copyTupleChangingFirstElement(myParam:AnyTuple[Int], newValue:Int) = myParam.copy(_1=newValue)
Run Code Online (Sandbox Code Playgroud)
如果所有要素都是如此,那就不难了Int.具有相同类型的所有元素的元组是a List,并且我们知道如何替换a的第一个元素List.我们需要将any转换TupleX为List,替换第一个元素,然后将其转换List为TupleX.是的,我们需要为所有X可能假设的值编写所有转换器.烦人但不难.
但在我们的例子中,并非所有元素都是Int.Tuple如果第一个元素是Int,我们想要处理不同类型的元素,就好像它们都是相同的一样.这就是所谓的
"摘要过度"
即以通用方式处理不同大小的元组,与其大小无关.为此,我们需要将它们转换为支持异类型的特殊列表,命名为HList
结论
案例类继承被弃用是非常合理的,因为您可以从邮件列表中的多个帖子中找到:http://www.scala-lang.org/node/3289
您有两种策略来处理您的问题:
如果您需要更改的字段数量有限,请使用@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
Run Code Online (Sandbox Code Playgroud)如果你真的想抽象arity,一个解决方案是使用由Miles Sabin开发的名为Shapeless的库.这里有一个问题在讨论后被问到:HLists只不过是一种复杂的编写元组的方式吗?但我告诉你这会让你头疼
ron*_*ron 13
如果两个案例类别随着时间的推移而发生分歧,以便它们具有不同的字段,那么共享copy方法将停止工作.
最好定义一个抽象def withMyParam(newParam: X): Base.更好的是,您可以引入一个抽象类型来在返回时保留case类类型:
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)
Run Code Online (Sandbox Code Playgroud)
Rég*_*les 13
TL; DR:我设法在Base上声明复制方法,同时仍允许编译器在派生的case类中自动生成其实现.这涉及一个小技巧(实际上我自己只是重新设计了类型层次结构),但至少它表明你确实可以在没有在任何派生的case类中编写样板代码的情况下使它工作.
首先,正如ron和Edmondo1984已经提到的,如果您的案例类有不同的字段,您将遇到麻烦.
我将严格遵循您的示例,并假设您的所有案例类都具有相同的字段(查看您的github链接,这似乎也是您的实际代码的情况).
鉴于所有案例类都具有相同的字段,自动生成的copy方法将具有相同的签名,这是一个良好的开端.然后Base就像你一样添加公共定义似乎是合理的:
abstract class Base{ def copy(myparam: String):Base }
现在问题是scala不会生成copy方法,因为基类中已经存在一个.
事实证明,还有另一种方法可以静态地确保Base具有正确的copy方法,并且它是通过结构类型和自我类型注释:
type Copyable = { def copy(myParam: String): Base }
sealed abstract class Base(val myParam: String) { this : Copyable => }
Run Code Online (Sandbox Code Playgroud)
与我们之前的尝试不同,这不会阻止scala自动生成copy方法.最后一个问题是:自我类型注释确保子类Base具有copy方法,但它不会公开提供Base:
val foo: Base = Foo("hello")
foo.copy()
scala> error: value copy is not a member of Base
Run Code Online (Sandbox Code Playgroud)
要解决此问题,我们可以添加从Base到Copyable的隐式转换.一个简单的演员会做,因为Base保证是可复制的:
implicit def toCopyable( base: Base ): Base with Copyable = base.asInstanceOf[Base with Copyable]
Run Code Online (Sandbox Code Playgroud)
总结一下,这给了我们:
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")
Run Code Online (Sandbox Code Playgroud)
额外效果:如果我们尝试使用不同的签名定义一个case类,我们会得到一个编译错误:
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
Run Code Online (Sandbox Code Playgroud)
完成一个警告:你应该只是修改你的设计,以避免诉诸上述技巧.在你的情况下,ron建议使用带有附加etype字段的单个案例类似乎更合理.