在Scala中实现ifTrue,ifFalse,ifSome,ifNone等,以避免if(...)和简单的模式匹配 [英] Implementing ifTrue, ifFalse, ifSome, ifNone, etc. in Scala to avoid if(...) and simple pattern matching

查看:410
本文介绍了在Scala中实现ifTrue,ifFalse,ifSome,ifNone等,以避免if(...)和简单的模式匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Scala中,我逐渐失去了以面向控制流的方式进行思考的Java/C习惯,并且习惯了先获取我感兴趣的对象,然后通常使用类似matchmap()foreach()表示集合.我非常喜欢它,因为现在感觉它是一种更自然,更直接的代码结构方式.

我希望一点一点地为条件编写相同的程序;即首先获取一个布尔值,然后match进行各种操作.但是,成熟的match对于此任务确实显得有些矫kill过正.

比较:

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

vs.我会用更接近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)使用吗?我知道使用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好.

我提供了我想出的基本实现;欢迎对此样式/技术和/或更好的实现方式发表一般评论!

解决方案

首先:我们可能无法重用else,因为它是关键字,并且使用反引号强制将其视为标识符是很丑陋的,所以我改用otherwise.

这是一个实现尝试.首先,使用pimp-my-library模式将ifTrueifFalse添加到Boolean.它们在返回类型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)

目前,它可以正常工作,让我写

someTest ifTrue {
  println("OK")
}

但是,如果没有下面的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)
}

当且仅当从前一个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函数参数,还可以传递给ifNone之后的otherwise函数参数.

In Scala, I have progressively lost my Java/C habit of thinking in a control-flow oriented way, and got used to go ahead and get the object I'm interested in first, and then usually apply something like a match or a map() or foreach() for collections. I like it a lot, since it now feels like a more natural and more to-the-point way of structuring my code.

Little by little, I've wished I could program the same way for conditions; i.e., obtain a Boolean value first, and then match it to do various things. A full-blown match, however, does seem a bit overkill for this task.

Compare:

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

vs. what I would write with style closer to Java:

if (obj.isSomethingValid)
  doX
else
  doY

Then I remembered Smalltalk's ifTrue: and ifFalse: messages (and variants thereof). Would it be possible to write something like this in Scala?

obj.isSomethingValid ifTrue doX else doY

with variants:

val v = obj.isSomethingValid ifTrue someVal else someOtherVal

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

Furthermore, could this style be made available to simple, two-state types like Option? I know the more idiomatic way to use Option is to treat it as a collection and call filter(), map(), exists() on it, but often, at the end, I find that I want to perform some doX if it is defined, and some doY if it isn't. Something like:

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

To me, this (still?) looks better than a full-blown match.

I am providing a base implementation I came up with; general comments on this style/technique and/or better implementations are welcome!

解决方案

First: we probably cannot reuse else, as it is a keyword, and using the backticks to force it to be seen as an identifier is rather ugly, so I'll use otherwise instead.

Here's an implementation attempt. First, use the pimp-my-library pattern to add ifTrue and ifFalse to Boolean. They are parametrized on the return type R and accept a single by-name parameter, which should be evaluated if the specified condition is realized. But in doing so, we must allow for an otherwise call. So we return a new object called Otherwise0 (why 0 is explained later), which stores a possible intermediate result as a Option[R]. It is defined if the current condition (ifTrue or ifFalse) is realized, and is empty otherwise.

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)

For now, this works and lets me write

someTest ifTrue {
  println("OK")
}

But, without the following otherwise clause, it cannot return a value of type R, of course. So here's the definition of Otherwise0:

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

It evaluates its passed named argument if and only if the intermediate result it got from the preceding ifTrue or ifFalse is undefined, which is exactly what is wanted. The type parametrization [S >: R] has the effect that S is inferred to be the most specific common supertype of the actual type of the named parameters, such that for instance, r in this snippet has an inferred type Fruit:

class Fruit
class Apple extends Fruit
class Orange extends Fruit

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

The apply() alias even allows you to skip the otherwise method name altogether for short chunks of code:

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

Finally, here's the corresponding pimp for 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)
}

Note that we now also need Otherwise1 so that we can conveniently passed the unwrapped value not only to the ifSome function argument, but also to the function argument of an otherwise following an ifNone.

这篇关于在Scala中实现ifTrue,ifFalse,ifSome,ifNone等,以避免if(...)和简单的模式匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆