在Scala中实现ifTrue、ifFalse、ifSome、ifNone等,以避免if(…)和简单的模式匹配

在Scala中实现ifTrue、ifFalse、ifSome、ifNone等,以避免if(…)和简单的模式匹配,scala,match,control-flow,Scala,Match,Control Flow,在Scala中,我逐渐失去了以面向控制流的方式思考Java/C的习惯,习惯于先获取我感兴趣的对象,然后通常对集合应用类似于匹配或映射()或foreach()。我非常喜欢它,因为它现在感觉像是一种更自然、更切中要害的构建代码的方式 渐渐地,我希望我能以同样的方式为条件编程;i、 例如,首先获取一个布尔值,然后匹配它以执行各种操作。然而,对于这项任务来说,一个全面的匹配,似乎有点过分了 比较: obj.isSomethingValid match { case true => doX

在Scala中,我逐渐失去了以面向控制流的方式思考Java/C的习惯,习惯于先获取我感兴趣的对象,然后通常对集合应用类似于
匹配
映射()
foreach()
。我非常喜欢它,因为它现在感觉像是一种更自然、更切中要害的构建代码的方式

渐渐地,我希望我能以同样的方式为条件编程;i、 例如,首先获取一个布尔值,然后匹配它以执行各种操作。然而,对于这项任务来说,一个全面的
匹配
,似乎有点过分了

比较:

obj.isSomethingValid match {
  case true => doX
  case false => doY
}
与我使用更接近Java的风格编写的内容相比:

if (obj.isSomethingValid)
  doX
else
  doY
然后我想起了Smalltalk的
ifTrue:
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
?我知道使用
选项更惯用的方法是将其视为一个集合,并在其上调用
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
}
对我来说,这(仍然?)看起来比全面的
匹配更好


我正在提供我提出的基本实现;欢迎对这种风格/技术和/或更好的实现发表一般性意见

首先:我们可能无法重用
else
,因为它是一个关键字,而使用反勾号强制将其视为标识符是相当难看的,因此我将使用
否则

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

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")
}
但是,如果没有下面的
否则
子句,它当然不能返回
R
类型的值。下面是
的定义,也就是0

class Otherwise0[R](intermediateResult: Option[R]) {
  def otherwise[S >: R](f: => S) = intermediateResult.getOrElse(f)
  def apply[S >: R](f: => S) = otherwise(f)
}
当且仅当它从前面的
ifTrue
ifFalse
得到的中间结果未定义时(这正是所需要的),它才计算其传递的命名参数。类型参数化
[S>:R]
的效果是,推断出
S
是命名参数的实际类型的最具体的通用超类型,例如,此代码段中的
R
具有推断出的类型
水果

class Fruit
class Apple extends Fruit
class Orange extends Fruit

val r = someTest ifTrue {
  new Apple
} otherwise {
  new Orange
}
apply()
别名甚至允许您跳过
否则
方法名称以获得短代码块:

someTest.ifTrue(10).otherwise(3)
// equivalently:
someTest.ifTrue(10)(3)
最后,这里是
选项
对应的皮条客:

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

请注意,我们现在还需要
其他方式1
,这样我们不仅可以方便地将展开值传递给
ifSome
函数参数,还可以传递给
的函数参数,否则
ifNone
之后,您可能会过于具体地看待这个问题。使用管道操作符可能会更好:

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"))
仅仅因为这些东西可能会有回报,并不意味着你必须避免它们。习惯语法确实需要一点时间;这可能是创建自己隐式的一个有效原因。但核心功能就在那里。(如果你自己创建,考虑<代码>折叠[b](f:a= >b)(g:=>b)< /c>;一旦你习惯了,缺少中间关键字实际上是相当好的。)

编辑:虽然管道的
|>
符号有点标准,但实际上我更喜欢
使用
作为方法名称,因为
定义重用[B,C](f:A=>B)(g:(A,B)=>C)=g(A,f(A))
似乎更自然。

为什么不这样使用它:

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


依我看,这很地道!:)

if
语句到底有什么问题?你可以使用这种在每种语言中广泛使用的简单、熟悉的控制结构,或者你可以创建一个对任何新开发人员来说都是心理减速的噱头抽象,并且一开始不会比
if
提供任何优势,但是对于
选项
,简单的
匹配
es如何呢?对于
选项
,有
getOrElse
。因此,处理每种情况的方法不同。因此,对我来说,使用ifTrue/ifFalse和ifSome/ifNone进行更一致的处理听起来不错……另请参见邮件列表上的讨论:感谢您的解释!事实上,这家管道运营商比我的建议更具普遍性。我发现缺少中间关键字会导致可读性降低,特别是当两个块互相跟随时……我可以轻松地将apply()方法添加到OtherwiseX对象中,并将其转发到其他对象,或者反过来。正如您所写的:习惯Option的本地语法需要时间……我仍然觉得在t