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

Jea*_*let 7 scala match control-flow

在Scala中,我逐渐失去了以控制流方式思考的Java/C习惯,并且习惯于继续前进并首先得到我感兴趣的对象,然后通常应用类似a match或a map()foreach()for的东西.集合.我非常喜欢它,因为它现在感觉像构建我的代码更自然,更直接的方式.

渐渐地,我希望我能以相同的方式为条件编程; 即,首先获得一个布尔值,然后match它做各种事情.match然而,对于这项任务来说,全面而言似乎有点过分.

相比:

obj.isSomethingValid match {
  case true => doX
  case false => doY
}
Run Code Online (Sandbox Code Playgroud)

与我更接近Java的风格相比:

if (obj.isSomethingValid)
  doX
else
  doY
Run Code Online (Sandbox Code Playgroud)

然后我想起了Smalltalk ifTrue:ifFalse:消息(及其变体).可以在Scala中写这样的东西吗?

obj.isSomethingValid ifTrue doX else doY
Run Code Online (Sandbox Code Playgroud)

与变种:

val v = obj.isSomethingValid ifTrue someVal else someOtherVal

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

此外,这种风格可以用于简单的两种类型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
}
Run Code Online (Sandbox Code Playgroud)

对我而言,这(仍然?)看起来比完全成型更好match.

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

Jea*_*let 14

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

这是一个实现尝试.首先,使用皮条客,我的图书馆模式,以增加ifTrueifFalseBoolean.它们在返回类型上进行参数化,R并接受单个by-name参数,如果实现了指定的条件,则应对其进行评估.但这样做,我们必须允许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)
Run Code Online (Sandbox Code Playgroud)

现在,这有效,让我写

someTest ifTrue {
  println("OK")
}
Run Code Online (Sandbox Code Playgroud)

但是,如果没有以下otherwise子句,它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)
}
Run Code Online (Sandbox Code Playgroud)

当且仅当它从前面获得的中间结果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
}
Run Code Online (Sandbox Code Playgroud)

apply()别名甚至可以让你跳过otherwise共方法名短代码块:

someTest.ifTrue(10).otherwise(3)
// equivalently:
someTest.ifTrue(10)(3)
Run Code Online (Sandbox Code Playgroud)

最后,这是相应的皮条客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)
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们现在还需要Otherwise1这样,以便我们可以方便地将unwrapped值传递给ifSome函数参数,还可以传递给otherwise后续函数的函数参数ifNone.


Rex*_*err 6

你可能也会特别关注这个问题.使用管道操作员可能会更好:

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

现在你可以

("fish".length > 5) |> (if (_) println("Hi") else println("Ho"))
Run Code Online (Sandbox Code Playgroud)

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

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

Option("fish").filter(_.length > 5).
  map (_ => println("Hi")).
  getOrElse(println("Ho"))
Run Code Online (Sandbox Code Playgroud)

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


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