简体   繁体   English

Scala - 创建类型集合的惯用方式

[英]Scala - Idiomatic way of creating a collection of types

Problem问题

Let's says that I have an ADT that look like that假设我有一个看起来像这样的 ADT

sealed trait TT
case class A(...) extends TT
case class B(...) extends TT
case class C(...) extends TT
// ... lot of others

And I have this function that return true for of subset of TT .我有这个函数,它为TT的子集返回 true 。 Let's say A and C .假设AC

def shouldIPublish(tt: TT): Boolean = ???

Constraints约束

  • the value tt we receive comes from a SortedSet that I can't really change.我们收到的值tt来自一个我无法真正改变的SortedSet Here is how the function shouldIPublish is called : tts.exists(shouldIPublish) ( tts is a SortedSet[TT] ).下面是函数shouldIPublish的调用方式: tts.exists(shouldIPublish)tts是一个SortedSet[TT] )。 It is erased.它被擦除。
  • I would like that my code to be testable.我希望我的代码是可测试的。 Which is not possible with the solution I have for the moment.对于我目前的解决方案,这是不可能的。 (see below) (见下文)
  • The code should compile for every TT type.代码应该针对每种TT类型进行编译。 If the actual type of tt is in a subset (ie A or C ) return true otherwise false .如果tt的实际类型在子集(即AC )中,则返回true否则返回false

My imperfect solution我不完美的解决方案

val shouldIPublishPF: PartialFunction[TT, Unit] = {
    case _: A =>
    case _: C =>
} 
def shouldIPublish(tt: TT): Boolean = shouldIPublishPF.isDefinedAt(tt)

I use partial functions here because the actual problem is a bit more complex.我在这里使用部分函数是因为实际问题有点复杂。 I use orElse to combine several partial functions together.我使用orElse将几个部分函数组合在一起。

This solution is easy to reason about and simple enough.这个解决方案很容易推理,也很简单。 But,但,

  • it does not seem very idiomatic.这似乎不是很惯用。 I means this tool (Partial Functions) does not seem to be the right one for the job (Testing if type is contained in a group of type).我的意思是这个工具(部分函数)似乎不适合这项工作(测试类型是否包含在一组类型中)。
  • I can't get every value defined for the partial function .我无法获得为部分函数定义的每个值 In that case it would be A and C .在这种情况下,它将是AC My unit test should fail when a new type is added to the partial function.当向部分函数添加新类型时,我的单元测试应该会失败。

Alternatives (I can think of)替代方案(我能想到)

Set of classes类集

Maybe the simplest solution.也许是最简单的解决方案。 I could have a Set and get class with .getClass .我可以有一个Set并使用.getClass获取类。 It seems ugly though.虽然看起来很丑。

Set(A.getClass, C.getClass)

Shapeless Coproduct无形的副产品

Looked quite promising at first.起初看起来很有希望。 Very idiomatic.非常地道。

type ShoudIPublish = A :+: C :+: CNil

Unfortunately, I don't see how to test that a type is "contained" in ShoudIPublish .不幸的是,我不知道如何在ShoudIPublish测试“包含” ShoudIPublish And I don't know if we can collect the list of all types (here A and C ).而且我不知道我们是否可以收集所有类型的列表(这里是AC )。


Do you have any suggestion?你有什么建议吗?

Type class looks like a solution (if you know the type of tt ie specific subtype of TT at compile time)类型类看起来像一个解决方案(如果你知道tt的类型,即在编译时TT特定子类型)

trait ShouldIPublish[T <: TT]
object ShouldIPublish {
  implicit val a: ShouldIPublish[A] = null
  implicit val c: ShouldIPublish[C] = null
}

def shouldIPublish[T <: TT : ShouldIPublish](tt: T) = ???

shouldIPublish(A())
//  shouldIPublish(B()) // doesn't compile
shouldIPublish(C())

Standard type class is shapeless.ops.coproduct.Inject标准类型类是shapeless.ops.coproduct.Inject

type ShoudIPublish = A :+: C :+: CNil

def shouldIPublish[T <: TT : Inject[ShoudIPublish, *]](tt: T) = ???

shouldIPublish(A())
//  shouldIPublish(B()) // doesn't compile
shouldIPublish(C())

OOP-style solution would be to use a trait (if you can modify the hierarchy) OOP 风格的解决方案是使用特征(如果您可以修改层次结构)

trait ShouldIPublish

sealed trait TT
case class A() extends TT with ShouldIPublish
case class B() extends TT
case class C() extends TT with ShouldIPublish 

def shouldIPublish[T <: TT with ShouldIPublish](tt: T) = ???

shouldIPublish(A())
//  shouldIPublish(B()) // doesn't compile
shouldIPublish(C())

Pattern-matching solution can work at runtime when you don't know specific subtype of TT at compile time当您在编译时不知道TT特定子类型时,模式匹配解决方案可以在运行时工作

def shouldIPublish(tt: TT) = tt match {
  case _: A => println("A")
  case _: C => println("C")
}

shouldIPublish(A(): TT)// A
shouldIPublish(B(): TT)// MatchError
shouldIPublish(C(): TT)// C

or或者

trait ShouldIPublish

sealed trait TT
case class A() extends TT with ShouldIPublish
case class B() extends TT
case class C() extends TT with ShouldIPublish 

def shouldIPublish(tt: TT) = tt match {
  case _: ShouldIPublish => println("A or C")
}

shouldIPublish(A(): TT)// A or C
shouldIPublish(B(): TT)// MatchError
shouldIPublish(C(): TT)// A or C

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM