为什么没有我在斯卡拉++?
我只是想知道为什么没有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
需要集合xs
和ys
并生成一个包含两者的新集合。
第三,斯卡拉试图鼓励你,而不是强迫你用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); } }
正如我们在输出中看到的那样, Incrementable
和int
的行为是不同的:
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
采用Function1
types的对象 ,其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
。 真正的int
在IntRef.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?