I'm wondering if if … else
could have been implemented in Predef
with special compiler treatment, in a similar way to what's being done with classOf[A]
: the definition is in Predef
, the implementation is filled in by the compiler.
Granted, many people would find reassuring to know that an if
is always an if
, and an else
is always an else
, no matter the context. However, defining else
as a method on the result type of if
would remove it from the list of keywords, and allow library designers to define their own else
methods. (I know I can use any keyword as an identifier with backticks, but something like `else`
just looks awful in code.) Such methods could be useful in cases discusses in situations such as this one, discussed on the mailing list, where people are forced to use otherwise
when defining methods that actually should be named e开发者_如何学编程lse
. (Also discussed on SO here and here.)
So:
- Would such an approach be possible, even in theory, or does it break some fundamental principle in Scala?
- What would the downsides be?
Maybe I don't understand your question, but you can already implement if ... else ...
as a library function. Consider this:
class If[A](condition: =>Boolean)(ifBlock: =>A) {
def els(elseBlock: =>A):A = condition match {
case true => ifBlock
case false => elseBlock
}
}
new If(2==3)(
println("equal")
) els (
println("not equal")
)
Of course this doesn't do exactly what if ... else ...
does, but with some polishing I think it would. I once implemented a very simple interpreter for a language that had pattern matching built in with if ... else ...
being implemented in much the same way I did here.
The short answer is "yes"; branching logic on some predicate can be implemented as a library function.
It's worth pointing out that, as Viktor Klang and others have noted, if/else is essentially folding a boolean. Folding is something we do frequently - sometimes it's clear and explicit, and sometimes not.
// Either#fold is explicit
scala> Left[String, Double]("fail") fold(identity, _ + 1 toString)
res0: java.lang.String = fail
scala> Right[String, Double](4) fold(identity, _ + 1 toString)
res1: java.lang.String = 5.0
Folding an option cannot be done explicitly, but we do it all the time.
// Option has no fold - wont compile!
Some(5) fold(1+, 0)
// .. but the following is equivalent and valid
scala> Some(5) map(1+) getOrElse(0)
res3: Int = 6
Branching logic on a boolean is also a fold, and you can pimp Boolean accordingly. Note the use of by-name parameters to achieve lazy evaluation. Without this feature, such an implementation wouldn't be possible.
// pimped Boolean - evaluates t when true, f when false
class FoldableBoolean(b: Boolean) {
def fold[A](t: => A, f: => A) =
if(b) t else f
}
implicit def b2fb(b: Boolean) = new FoldableBoolean(b)
Now we can fold Booleans:
scala> true fold("true!", "false")
res24: java.lang.String = true!
scala> false fold("true!", "false")
res25: java.lang.String = false
Not just if-else
, but any language feature can be overridden in a branch of the language known as "Scala Virtualized"
https://github.com/TiarkRompf/scala-virtualized
This forms the basis of the Delite project at Stanford PPL, and is also at the heart of the research being funded by Scala's EU grant. So you can reasonably expect it to be part of the core language at some point in the future.
Any object-oriented language (or any language with runtime polymorphism) can implement conditionals as a library feature, since method dispatch already is a more general form of conditional anyway. Smalltalk, for example, has absolutely no conditionals whatsoever except for method dispatch.
There is no need for any kind of compiler magic, except maybe for syntactic convenience.
In Scala, it would look maybe a little bit like this:
trait MyBooleanLike {
def iff[T <: AnyRef](thenn: => T): T
def iffElse[T](thenn: => T)(els: => T): T
def &&(other: => MyBoolean): MyBoolean
def ||(other: => MyBoolean): MyBoolean
def nott: MyBoolean
}
trait MyTruthiness extends MyBooleanLike {
def iff[T](thenn: => T) = thenn
def iffElse[T](thenn: => T)(els: => T) = thenn
def &&(other: => MyBoolean) = other
def ||(other: => MyBoolean) = MyTrue
def nott = MyFalse
}
trait MyFalsiness extends MyBooleanLike {
def iff[T](thenn: => T): T = null.asInstanceOf[T]
def iffElse[T](thenn: => T)(els: => T) = els
def &&(other: => MyBoolean) = MyFalse
def ||(other: => MyBoolean) = other
def nott = MyTrue
}
abstract class MyBoolean extends MyBooleanLike
class MyTrueClass extends MyBoolean with MyTruthiness {}
class MyFalseClass extends MyBoolean with MyFalsiness {}
object MyTrue extends MyTrueClass {}
object MyFalse extends MyFalseClass {}
Just add a little implicit conversion:
object MyBoolExtension {
implicit def boolean2MyBoolean(b: => Boolean) =
if (b) { MyTrue } else { MyFalse }
}
import MyBoolExtension._
And now we can use it:
object Main extends App {
(2 < 3) iff { println("2 is less than 3") }
}
[Note: my type-fu is rather weak. I had to cheat a little bit to get this to compile within a reasonable timeframe. Someone with a better understanding of Scala's type system may want to fix it up. Also, now that I look at it, 8 classes, traits and objects, two of them abstract, seems a little over-engineered ;-) ]
Of course, the same is true for pattern matching as well. Any language with pattern matching doesn't need other kinds of conditionals, since pattern matching is more general anyway.
[BTW: This is basically a port of this Ruby code I wrote a couple of years ago for fun.]
精彩评论