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

时间:2015-02-23 18:25:30

标签: scala generics casting implicit-conversion

我希望在没有隐式转换的情况下摆脱对通用(asInstanceOf[A])的运行时强制转换。

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

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

以下是演示问题的简化示例以及使用运行时强制转换的解决方案。

请不要挂断细节。

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

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

这是一种在股票总数发生变化时重新计算当前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]
}

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)

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

2 个答案:

答案 0 :(得分:5)

你有几个我能想到的选择,它主要归结为你想要的解决方案的一般性以及你可以忍受多少冗长的平衡。

<强> asInstanceOf

你的解决方案感觉很脏,但我认为它不是那么糟糕,而且这种情况很好。

<强>类型类

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

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

<强>光学

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

答案 1 :(得分:1)

这是@acjay建议的类型类方法

trait Copyable[A <: Share] {
  def copy(share: A, newPercentage: Float): A
}

object Copyable {
  implicit val commonShareCopyable: Copyable[CommonShare] =
    (share: CommonShare, newPercentage: Float) => share.copy(percentOfCompany = newPercentage)

  implicit val preferredShareCopyable: Copyable[PreferredShare] =
    (share: PreferredShare, newPercentage: Float) => share.copy(percentOfCompany = newPercentage)
}

implicit class WithRecalculateShare[A <: Share](share: A) {
  def recalculateShare(currentTotalShares: Int)(implicit ev: Copyable[A]): A =
    ev.copy(share, share.absolute / currentTotalShares.toFloat)
}

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

PreferredShare("2014-01-01", 50, 0.5f).recalculateShare(400)
// res1: PreferredShare = PreferredShare(2014-01-01,50,0.125)