6

我想摆脱运行时强制转换为泛型 ( 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]调用永远不会失败,但需要编译代码。有没有办法在没有隐式转换的情况下以类型安全的方式避免运行时强制转换?

4

2 回答 2

7

你有几个我能想到的选择,主要取决于你想要的解决方案的通用性和你可以容忍的冗长程度之间的平衡。

asInstanceOf

您的解决方案感觉很脏,但我认为这并没有那么糟糕,而且粗糙度得到了很好的控制。

类型类

在代码中保持关注点分离的同时为数据类型提供行为的一种很好的方法是丰富您的库/类型类模式。我希望我对此有一个完美的参考,但我没有。查找这些术语或“隐式类”,您应该能够找到足够的示例来了解情况。

您可以创建一个trait Copyable[A] { def copy(?): A }类型类 ( implicit class) 并为您的每种类型创建它的实例。这里的问题是它有点冗长,特别是如果您希望该copy方法完全通用。我将其参数列表作为问号留下,因为您可以根据实际需要对其进行严格调整,或者您可以尝试使其适用于 any case class,据我所知,这将非常困难。

光学

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

于 2015-02-23T18:47:19.590 回答
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)
于 2020-02-23T14:05:53.770 回答