在Scala中实现ifTrue,ifFalse,ifSome,ifNone等,以避免if(...)和简单的模式匹配 [英] Implementing ifTrue, ifFalse, ifSome, ifNone, etc. in Scala to avoid if(...) and simple pattern matching
问题描述
在Scala中,我逐渐失去了以面向控制流的方式进行思考的Java/C习惯,并且习惯了先获取我感兴趣的对象,然后通常使用类似match
或map()
或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模式将ifTrue
和ifFalse
添加到Boolean
.它们在返回类型R
上参数化,并接受单个by-name参数,如果实现了指定条件,则应对其进行评估.但是这样做,我们必须允许otherwise
调用.因此,我们返回了一个名为Otherwise0
的新对象(为什么稍后解释0),该对象将可能的中间结果存储为Option[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")
}
但是,如果没有下面的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)
}
当且仅当从前一个ifTrue
或ifFalse
获得的中间结果是不确定的时,它才评估其传递的命名参数.类型参数化[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屋!