从泛型函数返回案例类的副本,而无需运行时强制转换 [英] Return copy of case class from generic function without runtime cast

查看:97
本文介绍了从泛型函数返回案例类的副本,而无需运行时强制转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

解决方案

您有两种选择,我可以想到,这主要取决于您想要的解决方案的通用性和详细程度可以忍受.

asInstanceOf

您的解决方案让人感到肮脏,但我认为这并不是那么糟糕,而且毫不逊色.

类型分类

丰富代码库/类型类模式是一种在保持代码中关注点分离的同时为数据类型提供行为的好方法.我希望对此有一个完善的参考,但我没有.查找这些术语或隐式类",您应该能够找到足够的示例以获取实际经验.

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

光学

解决了这种尴尬的问题.您可能想要查看 Monocle ,这是解决此问题的一种很好的通用方法.尽管它仍然不能真正解决冗长的问题,但是如果您在整个项目中都反复出现此问题,特别是如果您发现自己试图在对象图的深处进行更改,那么这可能是解决问题的方法.

I want to get rid of a runtime cast to a generic (asInstanceOf[A]) without implicit conversions.

This happens when I have a fairly clean data model consisting of case classes with a common trait and want to implement a generic algorithm on it. As an example the resulting algorithm should take a class of type A that is a subclass of the trait T and is supposed to return a copy of the concrete class A with some updated field.

This is easy to achieve when I can simply add an abstract copy-method to the base trait and implement that in all sub-classes. However this potentially pollutes the model with methods only required by certain algorithms and is sometimes not possible because the model could be out of my control.

Here is a simplified example to demonstrate the problem and a solution using runtime casts.

Please don't get hung up on the details.

Suppose there is a trait and some case classes I can't change:

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

And here is a simple method to recalculate the current percentOfCompany when the total number of shares have changed and update the field in the case class

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]
}

Some example invocations on the 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)

So it works and as far as I understand the .asInstanceOf[A] call will never fail but is required to make the code compile. Is there a way to avoid the runtime cast in a type-safe manner without implicit conversions?

解决方案

You have a couple of choices I can think of, and it mostly comes down to a balance of how general of a solution you want and how much verbosity you can tolerate.

asInstanceOf

Your solution feels dirty, but I don't think it's all that bad, and the gnarliness is pretty well contained.

Typeclass

A great approach to providing behavior to data types while still maintaining separation of concerns in your code is the Enrich Your Library / typeclass pattern. I wish I had a perfect reference for this, but I don't. Look up those terms or "implicit class", and you should be able to find enough examples to get the drift.

You can create a trait Copyable[A] { def copy(?): A } typeclass (implicit class) and make instances of it for each of your types. The problem here is that it's kind of verbose, especially if you want that copy method to be fully generic. I left its parameter list as a question mark because you could just narrowly tailor it to what you actually need, or you could try to make it work for any case class, which would be quite difficult, as far as I know.

Optics

Lenses were made for solving this sort of awkwardness. You may want to check out Monocle, which is a nice generic approach to this issue. Although it still doesn't really solve the issue of verbosity, it might be the way to go if you have this issue recurring throughout your project, and especially if you find yourself trying to make changes deep within your object graph.

这篇关于从泛型函数返回案例类的副本,而无需运行时强制转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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