Scala隐式返回和if-then-else语句

时间:2012-09-22 05:41:58

标签: scala

我的一个问题是 - 如果我们依赖隐式退货,则容易出错

例如

def foo(bar: Int): Int =
{
    if (f1(bar)) 0
    if (f2(bar)) 1 
    else -1
}

有时我们只是忘记了else语句,为了解决这个问题,强制执行大括号,.e.g

def foo(bar: Int): Int =
{
    if (f1(bar)) {
        0
    } else if (f2(bar)) {
        1
    } else {
        -1
    }
}

但新风格太verbose恕我直言,还有什么方法可以解决?

5 个答案:

答案 0 :(得分:8)

实际上,我从来没有遇到过这个问题,而且我犯了很多愚蠢的错误。我怀疑你完全有能力学习不跳过else语句。 (请注意,大括号对任何内容都没有帮助;您仍然可以跳过中间的else。)

如果您真的发现这是一个问题,可以滥用match声明:

true match {
  case _ if f1(bar) => 0
  case _ if f2(bar) => 1
  case _ => -1
}

这会阻止你犯任何错误。或者你可以自己写一个实用方法:

trait Conditional[+A] {
  def apply[B >: A](p: => Boolean, b: => B): Conditional[B]
  def or[B >: A](default: => B): B
}
class FoundIt[+A](it: A) extends Conditional[A] {
  def apply[B >: A](p: => Boolean, b: => B) = this
  def or[B >: A](default: => B) = it
}
class NothingYet[+A] extends Conditional[A] {
  def apply[B >: A](p: => Boolean, b: => B) = {
    if (p) new FoundIt(b) else this
  }
  def or[B >: A](default: => B) = default
}
def ifelse[A](p: => Boolean, a: => A) = (new NothingYet[A]).apply(p,a)

ifelse( f1(bar), 0 )( f2(bar), 1 ) or -1 

对于长表达式来说这有点乱,所以你也可以(如果你想让它在那里工作的话,使用:paste将它固定在一个大块中的REPL中):

trait Predicate[+A] {
  def Or(p: => Boolean): Loadable[A]
  def Else[B >: A](default: => B): B
} 
trait Loadable[+A] {
  def Then[B >: A](b: => B): Predicate[B]
}
object NotYetTrue extends Predicate[Nothing] {
  def Or(p: => Boolean) = if (p) ActuallyLoad else SkipLoading
  def Else[B >: Nothing](default: => B) = default
}
object SkipLoading extends Loadable[Nothing] {
  def Then[B >: Nothing](b: => B) = NotYetTrue
}
object ActuallyLoad extends Loadable[Nothing] {
  def Then[B >: Nothing](b: => B) = new Loaded[B](b)
}
class Loaded[+A](a: A) extends Predicate[A] with Loadable[A] {
  def Or(p: => Boolean) = this
  def Else[B >: A](default: => B) = a
  def Then[B >: A](b: => B) = this
}
def If(p: => Boolean): Loadable[Nothing] = NotYetTrue.Or(p)

现在唯一的技巧是你必须在REPL中使用:paste来编写一个多行语句,并将延续句放在上一行的末尾:

If (f1(bar)) Then 0 Or
(f2(bar)) Then 1 Else
-1

你也可以在一行上使用Then { 0,然后在下一行使用} Or再次启动,或者用parens / braces和dots编写所有内容(这是REPL友好的):

If (f1(bar)) .Then (0) .
Or (f2(bar)) .Then (1) .
Else (-1)

无论如何,所有这些技巧都很好地说明了如何使用Scala构建复杂的DSL,但实际上并不是解决问题的最佳方法。你应该学会对if / else语句稍微小心一点;否则,人们会为你为什么不按标准方式做事而感到困惑。 (如果它的性能至关重要,可能会很慢 - match非常好,但If魔法在高性能循环中效果不佳。)

答案 1 :(得分:1)

我找到你了。没有人说,一个函数不止一个条件太多了!

折叠一些要应用的功能。也可以表达为collectFirst。

object Ifless extends App {
  def f1(i: Int) = i == 1
  def f2(i: Int) = i == 2
  def p1(i: Int) = if (f1(i)) Some(0) else None
  def p2(i: Int) = if (f2(i)) Some(1) else None
  def default(i: Int) = Some(-1)  //whatever
  def f(i: Int): Int = {
    val ps = List(p1 _, p2 _, default _)
    ps.foldLeft(None: Option[Int])((r,v) => r match {
      case None => v(i)
      case x => x
    }) getOrElse -1  //whatever
  }
  println(f(1))
  println(f(2))
  println(f(3))
}

即,

object Ifless2 extends App {
  // imagine this is not a switchable match
  def fs: List[PartialFunction[Int, Int]] = List (
    { case 1 => 0 },
    { case 2 => 1 },
    { case _ => -1 }
  )
  def f(i: Int): Int = {
    fs.collectFirst { case pf if pf.isDefinedAt(i) => pf(i) } getOrElse -1
  }
  println(f(1))
  println(f(2))
  println(f(3))
}

此外,它太糟糕了scalac不会发出像臭名昭着的有用的东西,“警告:纯粹的表达在声明位置没有任何作用。”

答案 2 :(得分:0)

目前还不清楚你在问什么。如果你想让scala在你输入错误的东西时知道你是if还是else if ......那么你可能运气不好。您必须键入正确的命令,无论是否出现编译时错误,添加或删除大括号都不会改变。

如果您只是想知道如何在没有括号的情况下编写if-elseif-else,那么:

if (f1(bar)) 0
else if (f2(bar)) 1 
else -1

答案 3 :(得分:0)

如果你有很多陈述,那么就需要大括号。在这个特定的例子中,你只有一个表达式,你可以删除括号,它会使错误更明显它会更简洁。此外,如果您使用IDE,您可以重新格式化代码,它有助于指出问题。

以下是没有任何大括号的格式示例。

  • foo是正确的版本。
  • foo2是您的有时我们只是忘了请参阅语法错误。
  • foo3也遗忘了else,但由于代码位于大括号块中,编译器会将if (f1(bar)) 0语句视为有效的隔离语句。

enter image description here

如果我在屏幕截图之前重新格式化了代码,那么在foo2中if / else不匹配会更明显。

答案 4 :(得分:0)

以下指南可能会有所帮助:

(1)没有if分支的else语句必须具有副作用(因为表达式的结果类型为Unit);因此,为避免上述错误,请始终在新行上使用大括号和正文,或在该语句下方强制使用空行。而不是......

if (f1(bar)) addToParent()  // addToParent has side-effect
if (f2(bar)) 1
else -1

......使用......

if (f1(bar)) addToParent()

if (f2(bar)) 1
else -1

...或...

if (f1(bar)) {
  addToParent()
}
if (f2(bar)) 1
else -1

结果是,您不应该看到两个if个关键字直接排列在彼此之下。换句话说,当你看到彼此之上有两个if时,你一定是犯了错误。

(2)如果您有多个支票,请使用显式括号,或者如果它们相当短(如您的示例中所示),请将它们放在一行:

if (f1(bar)) 0 else if (f2(bar)) 1 else -1

因为这不会编译:

if (f1(bar)) 0 if (f2(bar)) 1 else -1

<console>:3: error: ';' expected but 'if' found.
              if (f1(bar)) 0 if (f2(bar)) 1 else -1
                             ^

如果Scala是一种纯函数式语言或者可以识别副作用,编译器当然可以警告你。 IDE也可以帮助你。我在IntelliJ IDEA中试过这个(因为它有很多智能警告),但它没有发出警告。我认为这是一个很好的例子,演示编译器可以立即发现奇怪的分支,因为它们是未使用的文字(如你的情况),或者调用似乎没有副作用的方法(返回类型不是{{1并且根据样式指南方法没有空括号)。