3

我想编写透明地混合 Scala 选项和常规变量的条件语句。例如:

var o1 = Some(1)
var o2: Option[Int] = None

var x = 2

val test1 = x < 3 && o1<5  //=> should be true or Some(true)
val test2 = x < 3 && o2<5  //=> should be false or None
val test3 = x < 3 || o2<5  //=> should be true (o2 not evaluated)

我当然可以写

 test1 = x < 3 && o1.exists (_<5)

但我更喜欢更简洁的语法。

有什么提示吗?我应该用运算符扩展“选项”,还是使用隐式、范畴论或其他?

编辑:更正声明。

4

5 回答 5

4

使用隐式当然很容易:

implicit def enrichOptionInt(self: Option[Int]) = new {
  def <(i: Int) = self.exists(_ < i)
}

val test1 = x < 3 && o1 < 5  // True

或者,如果您希望它适用于任何类型的数字:

class EnrichedOptionNumeric[N: Numeric](self: Option[N]) {
  def <(n: N) = self.exists(v => implicitly[Numeric[N]].lt(v, n))
}
implicit def enrichOptionNumeric[N: Numeric](self: Option[N]) = new EnrichedOptionNumeric(self)

val oD = Some(2.0)
val test1 = x < 3 && o1 < 5    // true
val testD = x < 3 && oD < 5.0  // true

编辑以在评论中回答问题:

如果您想支持相等,那么不幸的是,您不能使用该运算符,==因为该运算符已经为Option. 如果已经为类定义了方法(或运算符),则永远不会触发隐式,因为 Scala 仅在无法识别被调用的方法时才查找隐式。

但是,您可以简单地定义一个不同的符号来表示“选项等于”。例如,您可以使用===. 为此,您只需将以下行添加到上述定义中EnrichedOptionNumeric

def ===(n: N) = self.exists(v => implicitly[Numeric[N]].equiv(v, n))

然后你可以这样做:

val testE = x < 3 && o1 === 1 // true
于 2012-05-20T21:31:21.157 回答
4

映射 from Option[Int]to怎么样Option[Boolean]

x < 3 && (o1 map {_ < 5} getOrElse false)
x < 3 && (o2 map {_ < 5} getOrElse false)
x < 3 || (o2 map {_ < 5} getOrElse false)
于 2012-05-20T21:35:09.673 回答
1

如果您有 Ordering[A] 但语义与您想要的不同(None 是最小值),Scala 实际上可以为 Option[A] 进行 Ordering。

此外,为了进行比较,两个值必须属于同一类型,因此您需要一种将 Ints 提升为 Option 的方法。为此,我添加了一个 opt 方法。

这是一个内部排序的例子:

import scala.math.Ordering.Implicits.infixOrderingOps
//This allows you to use method/operator syntax on anything with an Ordering

implicit def mkOption[A](a: A) = new { def opt: Option[A] = Some(a) }

var o1 = Some(1)
var o2: Option[Int] = None

var x = 2

val test1 = x < 3 && o1<5.opt  //=> true
val test2 = x < 3 && o2<5.opt  //=> true
val test3 = x < 3 || o2<5.opt  //=> true
None > 0.opt //=> false
None < 0.opt //=> true

为了更接近你的语义,我们可以定义一个新的排序

implicit def mkOptionOrdering[A: Ordering] = new Ordering[Option[A]] {
    def compare(a: Option[A], b: Option[A]): Int = {
      if (a.isEmpty || b.isEmpty) 0 
      else implicitly[Ordering[A]].compare(a.get, b.get)
    }
  }

现在您的测试将按照您的预期进行,另外 2 个额外的测试也将是错误的,但是这些语义相当奇怪,比较不相等的东西返回 0。

于 2012-05-23T04:16:37.443 回答
1

想想在这个问题的上下文中是否有比使用Option. 也许可以说没有限制,Double带有值的 aInfinity会起作用,或者Int带有a Integer.MAX_VALUE。然后Option完全从问题中删除。如果你不能,那么你可以使用

var o1 = Some (1)
var o2 = Option[Int] = None

var x = 2

val test1 = x < 3 && o1.getOrElse(Integer.MAX_VALUE)<5  //=> should be true or Some(true)
val test2 = x < 3 && o2.getOrElse(Integer.MAX_VALUE)<5  //=> should be false or None
val test3 = x < 3 || o2.getOrElse(Integer.MAX_VALUE)<5  //=> should be true (o2 not evaluated)
于 2012-05-20T22:37:26.283 回答
1

Tomasz Nurkiewicz 的答案可以用一些Scalaz糖更简洁,并Monoid使用Boolean.

幺半群的“零”值是false,因此o getOrElse false变成~o,使用~scalaz 中定义的一元运算符。(这里我使用的是 scalaz 6.0.4)

def p: Int => Boolean = _ < 3
def q: Int => Boolean = _ < 5
import scalaz._, Scalaz._

scala> val test1 = p(x) && ~(o1 map q)
test1: Boolean = true

scala> val test2 = p(x) && ~(o2 map q)
test2: Boolean = false

scala> val test3 = p(x) || ~(o2 map q)
test3: Boolean = true
于 2012-05-21T07:08:26.247 回答