从没有运行时强制转换的泛型函数返回case类的副本

n_l*_*n_l 5 generics casting scala implicit-conversion

我想摆脱对一个generic(asInstanceOf[A])的运行时强制转换而不进行隐式转换.

当我有一个相当干净的数据模型,包含具有共同特征的案例类并希望在其上实现通用算法时,就会发生这种情况.作为一个例子,生成的算法应该采用一个类的类A,它是一个子类,trait T并且应该返回A具有一些更新字段的具体类的副本.

当我可以简单地将一个abstract copy-method 添加到基本特征并在所有子类中实现它时,这很容易实现.然而,这可能会使用某些算法仅需要的方法来污染模型,有时不可能,因为模型可能不受我的控制.

下面是一个演示问题的简化示例和使用运行时强制转换的解决方案.

请不要挂断细节.

假设有一个特征和一些我不能改变的案例类:

trait Share {
  def absolute: Int  
}

case class CommonShare(
    issuedOn: String, 
    absolute: Int, 
    percentOfCompany: Float) 
  extends Share

case class PreferredShare(
    issuedOn: String, 
    absolute: Int, 
    percentOfCompany: Float)
  extends Share
Run Code Online (Sandbox Code Playgroud)

这是一种percentOfCompany在股票总数发生变化时重新计算当前流量的简单方法,并更新案例类中的字段

def recalculateShare[A <: Share](share: A, currentTotalShares: Int): A = {

  def copyOfShareWith(newPercentage: Float) = {
    share match {
      case common: CommonShare => common.copy(percentOfCompany = newPercentage)
      case preferred: PreferredShare => preferred.copy(percentOfCompany = newPercentage)
    }
  }

  copyOfShareWith(share.absolute / currentTotalShares.toFloat).asInstanceOf[A]
}
Run Code Online (Sandbox Code Playgroud)

REPL上的一些示例调用:

scala> recalculateShare(CommonShare("2014-01-01", 100, 0.5f), 400)
res0: CommonShare = CommonShare(2014-01-01,100,0.25)

scala> recalculateShare(PreferredShare("2014-01-01", 50, 0.5f), 400)
res1: PreferredShare = PreferredShare(2014-01-01,50,0.125)
Run Code Online (Sandbox Code Playgroud)

所以它的工作原理,据我所知,.asInstanceOf[A]调用永远不会失败,但需要编译代码.有没有办法在没有隐式转换的情况下以类型安全的方式避免运行时转换?

acj*_*jay 5

你有几个我能想到的选择,它主要归结为你想要的解决方案的一般性以及你可以容忍的冗长程度.

asInstanceOf

你的解决方案感觉很脏,但我认为它并不是那么糟糕,并且gnarliness非常好.

类型类

为数据类型提供行为同时仍然保持代码中关注点分离的一种很好的方法是Enrich Your Library/typeclass模式.我希望我有一个完美的参考,但我没有.查找这些术语或"隐含类",你应该能够找到足够的例子来获得漂移.

您可以创建一个trait Copyable[A] { def copy(?): A }类型类(implicit class)并为每个类型创建它的实例.这里的问题是它有点冗长,特别是如果你想让那个copy方法完全通用的话.我把它的参数列表作为一个问号留下了,因为你可以根据实际需要将它简单地定制,或者你可以尝试让它适用于任何一个case class,这是非常困难的,据我所知.

光学

镜头是为解决这种尴尬而制作的.您可能想查看Monocle,这是解决此问题的一种很好的通用方法.虽然它仍然没有真正解决冗长问题,但如果你在整个项目中反复出现这个问题,可能就是要走的路,特别是如果你发现自己试图在对象图中深入进行更改.