为什么没有我在斯卡拉++?

我只是想知道为什么没有i++来增加一个数字。 据我所知,像Ruby或Python这样的语言不支持它,因为它们是dynamictypes的。 所以显然我们不能像i++那样编写代码,因为也许i是一个string或其他东西。 但是Scala是静态types的 – 编译器绝对可以推断出如果是合法的,或者不把++放在variables后面。

那么,为什么i++不在斯卡拉存在?

Scala没有i++因为它是一种function性语言,在function语言中,避免了带有副作用的操作(纯function语言,根本没有副作用)。 i++的副作用是, i现在比以前大1。 相反,你应该尝试使用不可变的对象(例如val不是var )。

另外,Scala并不需要i++因为它提供了控制stream构造。 在Java和其他语言中,您经常需要使用i++来构build循环遍历数组的循环。 然而,在斯卡拉,你可以说你的意思: for(x <- someArray)someArray.foreach或沿着这些线的东西。 i++在命令式编程中非常有用,但是当你进入更高层次时,很less有必要(在Python中,我从来没有发现自己需要它)。

你注意到++ 可能在Scala中,但这并不是因为它没有必要,而只是阻塞了语法。 如果你真的需要它,比如说i += 1 ,但是因为Scala需要更多的时候用不可变的和丰富的控制stream来编程,所以你应该很less需要。 你当然可以自己定义它,因为运营商确实只是在斯卡拉的方法。

当然,如果你真的想要,你可以在斯卡拉

 import scalaz._, Scalaz._ case class IncLens[S,N](lens: Lens[S,N], num: Numeric[N]) { def ++ = lens.mods(num.plus(_, num.one)) } implicit def incLens[S,N: Numeric](lens: Lens[S,N]) = IncLens[S,N](lens, implicitly[Numeric[N]]) val i = Lens.lensu[Int,Int]((x, y) => y, identity) val imperativeProgram = for { _ <- i++; _ <- i++; x <- i++ } yield x def runProgram = imperativeProgram exec 0 

在这里你去:

 scala> runProgram res26: scalaz.Id.Id[Int] = 3 

没有必要诉诸变数的暴力。

Scala完全能够parsingi++并且可以通过对语言的小修改来修改variables。 但有很多原因不能。

首先,它只保存一个字符, i++i+=1 ,这对于添加新的语言function并不是非常节省。

其次, ++运算符在集合库中被广泛使用,其中xs ++ ys需要集合xsys并生成一个包含两者的新集合。

第三,斯卡拉试图鼓励你,而不是强迫你用function性的方式编写代码。 i++是一个可变的操作,所以它与Scala的想法不一致,使它特别容易。 (同样可以用一个语言特性来允许++改变一个variables。)

Scala没有++运算符,因为不可能在其中实现一个运算符。

编辑 :正如刚刚回应这个答案指出,斯卡拉2.10.0 可以通过使用macros实现增量运算符。 看到这个答案的细节,并采取下面的一切作为前斯卡拉2.10.0。

让我详细说明这一点,而且我将严重依赖Java,因为它实际上遭受同样的问题,但是如果我使用Java示例,人们可能会更容易理解它。

首先,需要注意的是,Scala的目标之一就是“内置”类不能具有图书馆无法复制的任何function。 当然,在Scala中,一个Int是一个类,而在Java中, int是一个基本types – 一个与类完全不同的types。

所以,为了支持i++typesInt Scala支持i++ ,我应该能够创build我自己的类MyInt也支持相同的方法。 这是Scala的驱动devise目标之一。

现在,Java自然不支持将符号作为方法名称,所以我们只需将其称为incr() 。 那么我们的意图是尝试创build一个方法incr()这样y.incr()就像i++一样工作。

这是第一个通过它:

 public class Incrementable { private int n; public Incrementable(int n) { this.n = n; } public void incr() { n++; } @Override public String toString() { return "Incrementable("+n+")"; } } 

我们可以用这个来testing它:

 public class DemoIncrementable { static public void main(String[] args) { Incrementable i = new Incrementable(0); System.out.println(i); i.incr(); System.out.println(i); } } 

一切似乎也工作:

 Incrementable(0) Incrementable(1) 

而现在,我将展示问题所在。 让我们改变我们的演示程序,并将其与Incrementable比较int

 public class DemoIncrementable { static public void main(String[] args) { Incrementable i = new Incrementable(0); Incrementable j = i; int k = 0; int l = 0; System.out.println("i\t\tj\t\tk\tl"); System.out.println(i+"\t"+j+"\t"+k+"\t"+l); i.incr(); k++; System.out.println(i+"\t"+j+"\t"+k+"\t"+l); } } 

正如我们在输出中看到的那样, Incrementableint的行为是不同的:

 ijkl Incrementable(0) Incrementable(0) 0 0 Incrementable(1) Incrementable(1) 1 0 

问题是我们通过改变Incrementable实现incr() ,这不是原语的工作原理。 Incrementable需求是不可变的,这意味着incr()必须产生一个新的对象。 让我们做一个天真的改变:

 public Incrementable incr() { return new Incrementable(n + 1); } 

但是,这不起作用:

 ijkl Incrementable(0) Incrementable(0) 0 0 Incrementable(0) Incrementable(0) 1 0 

问题是,虽然, incr() 创build了一个新的对象,该新的对象还没有被分配i 。 在Java或Scala中没有现成的机制可以让我们用与++完全相同的语义实现这个方法。

现在,这并不意味着Scala不可能做出这样的事情。 如果Scala支持通过引用传递参数(请参阅本维基百科文章中的 “通过引用调用”),就像C ++一样,那么我们可以实现它!

这里是一个虚构的实现,假设与C ++中的引用符号相同。

 implicit def toIncr(Int &n) = { def ++ = { val tmp = n; n += 1; tmp } def prefix_++ = { n += 1; n } } 

这要么需要JVM支持,要么Scala编译器需要一些严格的机制。

实际上,斯卡拉的做法与创build闭包时需要的东西类似 – 其中一个后果就是原始的Int变成了盒子,可能会对性能造成严重影响。

例如,考虑这个方法:

  def f(l: List[Int]): Int = { var sum = 0 l foreach { n => sum += n } sum } 

传递给foreach的代码{ n => sum += n } 不是这个方法的一部分。 方法foreach采用Function1types的对象 ,其apply方法实现了这个小代码。 这意味着{ n => sum += n }不仅仅是一个不同的方法,而是完全不同的一个类。 然而,它可以像++运算符那样改变和的值。

如果我们使用javap来查看它,我们会看到:

 public int f(scala.collection.immutable.List); Code: 0: new #7; //class scala/runtime/IntRef 3: dup 4: iconst_0 5: invokespecial #12; //Method scala/runtime/IntRef."<init>":(I)V 8: astore_2 9: aload_1 10: new #14; //class tst$$anonfun$f$1 13: dup 14: aload_0 15: aload_2 16: invokespecial #17; //Method tst$$anonfun$f$1."<init>":(Ltst;Lscala/runtime/IntRef;)V 19: invokeinterface #23, 2; //InterfaceMethod scala/collection/LinearSeqOptimized.foreach:(Lscala/Function1;)V 24: aload_2 25: getfield #27; //Field scala/runtime/IntRef.elem:I 28: ireturn 

请注意,不是创build一个int局部variables,而是在堆上(在0处)创build一个IntRef ,这是装箱的int 。 真正的intIntRef.elem里面,就像我们在25上看到的那样。让我们看一下用while循环实现的相同的东西来清楚区别:

  def f(l: List[Int]): Int = { var sum = 0 var next = l while (next.nonEmpty) { sum += next.head next = next.tail } sum } 

那变成:

 public int f(scala.collection.immutable.List); Code: 0: iconst_0 1: istore_2 2: aload_1 3: astore_3 4: aload_3 5: invokeinterface #12, 1; //InterfaceMethod scala/collection/TraversableOnce.nonEmpty:()Z 10: ifeq 38 13: iload_2 14: aload_3 15: invokeinterface #18, 1; //InterfaceMethod scala/collection/IterableLike.head:()Ljava/lang/Object; 20: invokestatic #24; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I 23: iadd 24: istore_2 25: aload_3 26: invokeinterface #29, 1; //InterfaceMethod scala/collection/TraversableLike.tail:()Ljava/lang/Object; 31: checkcast #31; //class scala/collection/immutable/List 34: astore_3 35: goto 4 38: iload_2 39: ireturn 

上面没有创build对象,不需要从堆中获取某些东西。

因此,总而言之,Scala需要额外的function来支持用户可以定义的增量操作符,因为它避免了给自己的内置类提供外部库不可用的类。 一个这样的function是通过引用传递参数,但JVM不提供对它的支持。 斯卡拉类似于通过引用来调用,并使用装箱,这会严重影响性能(最有可能提出一个增量运算符!)。 因此,在没有JVM支持的情况下,这不太可能。

另外需要注意的是,Scala具有不同的function倾向,在可变性和副作用方面具有不变性和参考透明性。 通过引用的唯一目的是引起呼叫者的副作用! 虽然这样做可以在很多情况下带来性能上的优势,但是对于Scala来说却是非常不利的,所以我怀疑这个引用是否会成为Scala的一部分。

其他答案已经正确地指出, ++运算符在函数式编程语言中既不是特别有用也不可取的。 我想补充说,既然Scala 2.10,你可以添加一个++运算符,如果你想。 这里是如何:

您需要一个隐式macros,将int转换为具有++方法的实例。 ++方法由macros写入,该macros可以访问调用++方法的variables(而不是其值)。 这是macros的实现:

 trait Incrementer { def ++ : Int } implicit def withPp(i:Int):Incrementer = macro withPpImpl def withPpImpl(c:Context)(i:c.Expr[Int]):c.Expr[Incrementer] = { import c.universe._ val id = i.tree val f = c.Expr[()=>Unit](Function( List(), Assign( id, Apply( Select( id, newTermName("$plus") ), List( Literal(Constant(1)) ) ) ) )) reify(new Incrementer { def ++ = { val res = i.splice f.splice.apply res } }) } 

现在,只要隐式转换macros在范围内,就可以编写

 var i = 0 println(i++) //prints 0 println(i) //prints 1 

Rafe的答案对于为什么像i ++这样的东西不属于Scala的理由是正确的。 不过我有一个挑剔。 实际上不可能在不改变语言的情况下在Scala中实现i ++。

在斯卡拉,++是一个有效的方法,没有方法意味着分配。 只有=可以做到这一点。

像C ++和Java这样的语言将++视为特别意味着增量和赋值。 斯卡拉特别对待,并以不一致的方式。

在Scala中编写i += 1 ,编译器首先在Int上查找名为+=的方法。 它不在那里,所以接下来它就是=上的魔法,并试图编译该行,就像读取i = i + 1 。 如果你写i++那么Scala会调用i的方法++ ,并把结果赋给…什么都不是。 因为只有=意味着分配。 你可以写i ++= 1但是这种方式打破了目的。

Scala支持像+=这样的方法名的事实已经引起争议,有些人认为它是运算符重载。 他们可能为++添加了特殊的行为,但不再是一个有效的方法名称(如= ),还有一件事要记住。

很多语言不支持++符号,比如Lua。 在被支持的语言中,它经常是混淆和错误的来源,所以它作为语言特征的质量是可疑的,并且与i += 1或者甚至i = i + 1的替代相比,节省这样的小angular色是相当无意义的。

这与语言的types系统无关。 虽然大多数静态types语言确实提供了,而大多数dynamictypes却没有,但这是相关性,绝​​对不是原因。

斯卡拉鼓励使用FP风格, i++肯定不是。

要问的问题是为什么应该有这样一个运营商,而不是为什么不应该有这样的运营商。 Scala会被改进吗?

++运算符是单用途的,并且具有可以改变variables值的运算符会导致问题。 编写令人困惑的expression式很容易,即使语言定义了i = i + i++含义,例如,这就是很多需要记住的细节规则。

顺便说一句,关于Python和Ruby的推理是错误的。 在Perl中,您可以编写$i++++$i就好了。 如果$i原来是不能增加的东西,那么会出现运行时错误。 这不是Python或Ruby,因为语言devise者不认为这是一个好主意,不是因为它们像Perl一样dynamicinput。

虽然你可以模拟它。 作为一个微不足道的例子:

 scala> case class IncInt(var self: Int = 0) { def ++ { self += 1 } } defined class IncInt scala> val i = IncInt() i: IncInt = IncInt(0) scala> i++ scala> i++ scala> i res28: IncInt = IncInt(2) 

添加一些隐式转换,你很好去。 然而,这样的问题变成:为什么不存在具有此function的可变RichInt?