7

在 Scala 中,我逐渐失去了 Java/C 以面向控制流的方式思考的习惯,并习惯于先获取我感兴趣的对象,然后通常应用诸如 amatch或 amap()foreach()for之类的东西收藏品。我非常喜欢它,因为它现在感觉像是一种更自然、更直接的方式来构建我的代码。

渐渐地,我希望我能以同样的方式为条件编程;即,首先获得一个布尔值,然后match它来做各种事情。match然而,对于这项任务来说,一个成熟的 .

比较:

obj.isSomethingValid match {
  case true => doX
  case false => doY
}

与我用更接近 Java 的风格编写的内容相比:

if (obj.isSomethingValid)
  doX
else
  doY

然后我想起了 SmalltalkifTrue:ifFalse:消息(及其变体)。是否有可能在 Scala 中编写这样的东西?

obj.isSomethingValid ifTrue doX else doY

有变体:

val v = obj.isSomethingValid ifTrue someVal else someOtherVal

// with side effects
obj.isSomethingValid ifFalse {
  numInvalid += 1
  println("not valid")
}

此外,这种风格是否可以用于简单的两种状态类型,例如Option?我知道更惯用的使用方式Option是将其视为一个集合并在其上调用filter(), map(),exists()但通常,最后,我发现如果定义了它,我想执行一些doX,如果没有定义,则执行一些doY。就像是:

val ok = resultOpt ifSome { result =>
  println("Obtained: " + result)
  updateUIWith(result) // returns Boolean
} else {
  numInvalid += 1
  println("missing end result")
  false
}

对我来说,这(仍然?)看起来比成熟的match.

我提供了一个我想出的基本实现;欢迎对这种风格/技术和/或更好的实现发表一般评论!

4

3 回答 3

14

首先:我们可能不能重用else,因为它是一个关键字,并且使用反引号来强制它被视为一个标识符是相当难看的,所以我将使用它otherwise

这是一个实现尝试。首先,使用 pimp-my-library 模式添加ifTrueifFalseBoolean. 它们在返回类型上进行参数化,R并接受单个按名称参数,如果实现了指定的条件,则应评估该参数。但在这样做时,我们必须允许otherwise调用。所以我们返回一个新的对象,叫做Otherwise0(为什么后面会解释为什么是 0),它将一个可能的中间结果存储为Option[R]. 如果当前条件(ifTrueifFalse)被实现,则定义它,否则为空。

class BooleanWrapper(b: Boolean) {
  def ifTrue[R](f: => R) = new Otherwise0[R](if (b) Some(f) else None)
  def ifFalse[R](f: => R) = new Otherwise0[R](if (b) None else Some(f))
}
implicit def extendBoolean(b: Boolean): BooleanWrapper = new BooleanWrapper(b)

现在,这可行,让我写

someTest ifTrue {
  println("OK")
}

但是,如果没有以下otherwise子句,它当然不能返回 type 的值R。所以这里是 的定义Otherwise0

class Otherwise0[R](intermediateResult: Option[R]) {
  def otherwise[S >: R](f: => S) = intermediateResult.getOrElse(f)
  def apply[S >: R](f: => S) = otherwise(f)
}

当且仅当它从前面得到的中间结果ifTrueifFalse未定义时,它才会评估其传递的命名参数,这正是我们想要的。类型参数化[S >: R]具有S被推断为命名参数的实际类型的最具体的常见超类型的效果,例如,r在此代码段中具有推断类型Fruit

class Fruit
class Apple extends Fruit
class Orange extends Fruit

val r = someTest ifTrue {
  new Apple
} otherwise {
  new Orange
}

别名甚至允许您在一小段代码中完全apply()跳过方法名称:otherwise

someTest.ifTrue(10).otherwise(3)
// equivalently:
someTest.ifTrue(10)(3)

最后,这是相应的皮条客Option

class OptionExt[A](option: Option[A]) {
  def ifNone[R](f: => R) = new Otherwise1(option match {
    case None => Some(f)
    case Some(_) => None
  }, option.get)
  def ifSome[R](f: A => R) = new Otherwise0(option match {
    case Some(value) => Some(f(value))
    case None => None
  })
}

implicit def extendOption[A](opt: Option[A]): OptionExt[A] = new OptionExt[A](opt)

class Otherwise1[R, A1](intermediateResult: Option[R], arg1: => A1) {
  def otherwise[S >: R](f: A1 => S) = intermediateResult.getOrElse(f(arg1))
  def apply[S >: R](f: A1 => S) = otherwise(f)
}

请注意,我们现在还需要Otherwise1方便地将解包后的值不仅传递给ifSome函数参数,还可以传递给otherwise后续 an的函数参数ifNone

于 2011-04-13T18:46:34.183 回答
6

您可能过于具体地看待这个问题。使用管道运算符可能会更好:

class Piping[A](a: A) { def |>[B](f: A => B) = f(a) }
implicit def pipe_everything[A](a: A) = new Piping(a)

现在你可以

("fish".length > 5) |> (if (_) println("Hi") else println("Ho"))

诚然,它不像你想要达到的那样优雅,但它具有惊人的多功能性的巨大优势——任何时候你想首先提出一个论点(不仅仅是布尔值),你可以使用它.

此外,您已经可以按照自己的方式使用选项:

Option("fish").filter(_.length > 5).
  map (_ => println("Hi")).
  getOrElse(println("Ho"))

仅仅因为这些东西可以取一个返回值并不意味着你必须避免它们。确实需要一点时间来适应语法。这可能是创建自己的隐式的正当理由。但是核心功能就在那里。(如果您确实创建了自己的,请考虑fold[B](f: A => B)(g: => B);一旦您习惯了它,缺少中间关键字实际上是相当不错的。)


编辑:虽然|>管道的符号有点标准,但我实际上更喜欢use作为方法名称,因为那样def reuse[B,C](f: A => B)(g: (A,B) => C) = g(a,f(a))看起来更自然。

于 2011-04-13T19:08:32.017 回答
2

为什么不像这样使用它:

val idiomaticVariable = if (condition) { 
    firstExpression
  } else { 
    secondExpression 
  } 

?

IMO,它非常地道!:)

于 2011-04-14T09:12:59.070 回答