【问题标题】:Scala - Idiomatic way of creating a collection of typesScala - 创建类型集合的惯用方式
【发布时间】:2019-12-10 17:21:00
【问题描述】:

问题

假设我有一个看起来像这样的 ADT

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

我有这个函数,它为TT 的子集返回 true。假设AC

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

约束

  • 我们收到的值tt 来自SortedSet,我无法真正更改。以下是函数shouldIPublish 的调用方式:tts.exists(shouldIPublish)ttsSortedSet[TT])。它已被删除。
  • 我希望我的代码是可测试的。我目前的解决方案是不可能的。 (见下文)
  • 代码应该为每个TT 类型编译。如果tt 的实际类型在一个子集中(即AC),则返回true,否则返回false

我不完美的解决方案

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

我在这里使用偏函数是因为实际问题要复杂一些。我使用orElse 将几个偏函数组合在一起。

这个解决方案很容易推理并且足够简单。但是,

  • 这似乎不是很地道。我的意思是这个工具(部分函数)似乎不适合这项工作(测试类型是否包含在一组类型中)。
  • 我无法获得为偏函数定义的每个值。在这种情况下,它将是 AC。当向部分函数添加新类型时,我的单元测试应该会失败。

替代方案(我能想到的)

类集

也许是最简单的解决方案。我可以拥有Set 并使用.getClass 上课。不过看起来很丑。

Set(A.getClass, C.getClass)

无形联积

起初看起来很有希望。非常地道。

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

不幸的是,我看不到如何测试 ShoudIPublish 中的类型是否“包含”。而且我不知道我们是否可以收集所有类型的列表(这里是AC)。


你有什么建议吗?

【问题讨论】:

    标签: scala shapeless category-theory


    【解决方案1】:

    类型类看起来像一个解决方案(如果你知道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())
    

    标准类型类是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 风格的解决方案是使用特征(如果您可以修改层次结构)

    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())
    

    当您在编译时不知道 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
    

    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
    

    【讨论】:

    • 感谢您的回答 Dmyto。我更新了我的帖子以添加详细信息。 1 - 使用Inject 看起来很有希望,但类型已被删除,所以我认为我不能拥有这种签名def shouldIPublish[T &lt;: TT](tt: T)。 (见我上面的编辑)。我希望我的代码在收到另一个值时编译事件。在这种情况下,它将返回 false 2 - 我宁愿不更新特征层次结构。
    • @Dnomyar 因为你有SortedSet[TT],所以它的所有元素都有TT类型,而不是特定的子类型。这意味着所有编译时解决方案(类型类、隐式、无形...)都将不起作用。所以你将不得不处理模式匹配或反射。
    • @Dnomyar 我不确定我是否理解用我目前的解决方案”。无论如何,您必须以某种方式指定“真实”系列中的类:def shouldIPublish(tt: TT): Boolean = tt match { case _ : A | _ : C =&gt; true; case _ =&gt; false }
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-12
    • 1970-01-01
    • 2015-02-07
    • 1970-01-01
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多