【问题标题】:Scala: using None for other purposes than empty OptionScala:将 None 用于空选项以外的其他目的
【发布时间】:2011-08-30 13:23:37
【问题描述】:

根据文档,None 对象旨在“表示不存在的值”。据我所知,它主要用作空的Option。但是您认为将其用于其他目的是否是个好主意。例如,在我的库中,我想要一个通用的“Empty”对象,它可以分配给各种缺失值,我只需根据需要将“Empty”值隐式转换为我的类型:

// In library:
trait A {
  implicit def noneToT1(none: Option[Nothing]): T1 = defaultT1
  implicit def noneToT2(none: Option[Nothing]): T2 = defaultT2
  def f1: T1
  def f2: T2
}
// In the code that uses the library
class EmptyA extends A {
  def f1 = None      
  def f2 = None      
}

不以这种方式(错误)使用None 的一个原因是用户期望f1f2 分别返回Option[T1]Option[T2]。他们没有。当然,我可以有def f1: Option[T1],但在这种情况下,这些值实际上不是可选的,它们只能有一些默认的空值或真实值,我只想“在后台”创建默认值并拥有在整个库中以某种统一的方式说“默认”或“空”。所以问题是,我应该使用None 来表达这种“默认”还是使用一些自定义类型?现在我用的是我自己的object Empty,不过感觉有点多余。

编辑: 为了说明我的问题,我将添加我现在正在使用的代码:

// In library:
trait Empty
object Empty extends Empty

trait A {
  implicit def emptyToT1(none: Empty): T1 = defaultT1
  implicit def emptyToT2(none: Empty): T2 = defaultT2
  def f1: T1
  def f2: T2
}
// In the code that uses the library
class EmptyA extends A {
  def f1 = Empty
  def f2 = Empty
}
class HalfFullA extends A {
  def f1 = Empty
  def f2 = someValue2
}
class FullA extends A {
  def f1 = someValue1
  def f2 = someValue2
}

我的问题很简单:使用 scala 的 None 而不是我的 Empty 是个好主意吗?

【问题讨论】:

    标签: scala option implicit-conversion


    【解决方案1】:

    我会为此使用类型类:

    trait WithDefault[T] {
      def default: T
    }
    
    object WithDefault {
      // if T1 is an existing class
      implicit val t1Default = new WithDefault[T1] {def default = defaultT1}
    }
    
    //if T2 is your own class:
    class T2 ...
    object T2 {
      implicit val withDefault = new WithDefault[T2] {def default = defaultT2}
    }
    

    然后找个方便的地方:

    def default[T : WithDefault] = implicitly[WithDefault[T]].default
    

    并使用:

    class EmptyA {
      def f1 = default[T1]
      def f2 = default[T2]
    }
    

    更新:为了适应 Vilius,可以试试这个:

    def default = new WithDefault[Nothing]{def default = error("no default")}
    
    implicit def toDefault[U, T](dummy: WithDefault[U])(implicit withDefault: WithDefault[T]): T = withDefault.default
    
    class EmptyA {
      def f1: T1 = default
      def f2: T2 = default
    }
    

    这比 OP 最初的尝试有好处,因为每个新类都可以定义自己的默认值(以及 WithDefault 中的其他值),而不是将所有内容都包含在 trait A 中。

    但是,这不起作用。见https://issues.scala-lang.org/browse/SI-2046

    要解决这个问题:

    trait A {
        def f1: T1
        def f2: T2
    
        implicit def toT1Default(dummy: WithDefault[Nothing]) = toDefault[T1](dummy)
        implicit def toT2Default(dummy: WithDefault[Nothing]) = toDefault[T2](dummy)
    }
    
    class EmptyA extends A {
       def f1 = default
       def f2 = default
    }
    

    【讨论】:

    • 这是一个不错的解决方案,我还没有想到。我喜欢它,因为你有一个创建默认值的统一方法,但仍然不会让用户对方法的类型感到困惑。然而在这个特殊的库中,这有点矫枉过正,而且看起来仍然很丑 - 例如,我的一些方法返回部分函数,​​所以我必须写:default[PartialFunction[SomeType, OtherType]](当然我可以有类型别名,但这将是一个矫枉过正的事件)
    • 这种情况下Scala不能推导出类型参数吗?
    • 更新了我的答案。 Scala 无法推断类型,因为 if 推断出表达式类型并从中推断出方法的返回类型,而不是相反。
    【解决方案2】:

    我认为你应该选择更简单的东西。例如,从您的示例开始,并删除我们很快就能找到的无关内容,

    trait A {
      def noT1 = defaultT1
      def noT2 = defaultT2
      def f1: T1
      def f2: T2
    }
    
    class EmptyA extends A {
      def f1 = noT1      
      def f2 = noT2      
    }
    

    我真的不认为添加选项或隐含会增加任何价值,至少不会增加任何价值,除非该问题还有其他未说明的上下文。

    【讨论】:

    • 问题是,我想用一个词来表达noT1noT2 以及可能许多其他noXX,这在我的图书馆中是统一的。在某些情况下,这可以是一个空列表或集合,在其他情况下,它可以是执行某些默认“空”操作的部分函数,​​或其他。我想将所有NilSet.empty{ case _ => } 隐藏在一些神奇的单字表达式下。问题是,我应该为此使用 None 还是发明其他东西(我现在正在做 - 我使用 object Empty)?
    【解决方案3】:

    如果您不能或不想使用继承定义默认值,我建议保留新对象。将None 重用于Some 以外的其他东西似乎是错误的,并不能真正为您节省太多。

    【讨论】:

      猜你喜欢
      • 2021-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-13
      • 2014-05-05
      • 2017-07-11
      • 2012-10-04
      • 2017-07-22
      相关资源
      最近更新 更多