我只是想知道为什么没有i++
来增加数字。据我所知,Ruby或Python等语言不支持它,因为它们是动态类型的。所以显然我们不能编写像i++
这样的代码,因为i
可能是一个字符串或其他东西。但是Scala是静态类型的 - 编译器绝对可以推断,如果将++
放在变量后面是合法的还是合法的。
那么,为什么Scala中不存在i++
?
答案 0 :(得分:70)
Scala没有i++
,因为它是一种函数式语言,在函数式语言中,避免了带副作用的操作(在纯函数式语言中,根本不允许任何副作用)。 i++
的副作用是i
现在比之前大1。相反,您应该尝试使用不可变对象(例如val
而非var
)。
此外,Scala并不真正需要i++
,因为它提供了控制流构造。在Java和其他人中,您经常需要i++
来构造迭代数组的while
和for
循环。但是,在Scala中,您可以说出您的意思:for(x <- someArray)
或someArray.foreach
或其他内容。 i++
在命令式编程中很有用,但是当你达到更高级别时,很少需要(在Python中,我从未发现自己需要它一次)。
你可以看到++
可以在Scala中,但这并不是因为它没有必要,只会堵塞语法。如果你确实需要它,请说i += 1
,但是因为Scala需要更频繁地使用不可变和丰富的控制流来编程,所以你应该很少需要。你当然可以自己定义它,因为运算符确实只是Scala中的方法。
答案 1 :(得分:35)
当然,如果你真的想要,你可以在Scala中拥有它:
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
无需对变量采取暴力行为。
答案 2 :(得分:14)
Scala完全能够解析i++
,并且通过对语言的一些小修改,可以修改变量。但是有很多理由不这样做。
首先,它只保存一个字符i++
与i+=1
,这对于添加新语言功能而言并不算节省。
其次,++
运算符在集合库中广泛使用,其中xs ++ ys
采用集合xs
和ys
并生成包含两者的新集合。
第三,Scala试图鼓励你,而不是强迫你,以功能的方式编写代码。 i++
是一个可变操作,因此它与Scala的想法不一致,使其变得特别容易。 (同样,语言功能允许++
改变变量。)
答案 3 :(得分:12)
Scala没有++
运算符,因为无法在其中实现一个运算符。
编辑:正如刚才回答的那样,Scala 2.10.0 可以通过使用宏来实现增量运算符。有关详细信息,请参阅this answer,并将以下所有内容视为Scala 2.10.0之前的版本。
让我详细说明这一点,我将严重依赖Java,因为它实际上遇到了同样的问题,但如果我使用Java示例,人们可能更容易理解它。
首先,需要注意的是,Scala的目标之一是“内置”类不能具有库无法复制的任何功能。当然,在Scala中,Int
是一个类,而在Java中,int
是一个原语 - 一种完全不同于类的类型。
因此,对于Scala来支持i++
类型为i
的{{1}},我应该能够创建自己的类Int
,同时支持相同的方法。这是Scala的驱动设计目标之一。
现在,Java自然不支持将符号作为方法名称,所以我们只需将其称为MyInt
。我们的目的是尝试创建一个方法incr()
,使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+")";
}
}
一切似乎都有效:
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
问题是我们通过改变i j k l
Incrementable(0) Incrementable(0) 0 0
Incrementable(1) Incrementable(1) 1 0
来实现incr()
,这不是原语的工作原理。 Incrementable
需要是不可变的,这意味着Incrementable
必须生成 new 对象。让我们做一个天真的改变:
incr()
然而,这不起作用:
public Incrementable incr() {
return new Incrementable(n + 1);
}
问题是,当i j k l
Incrementable(0) Incrementable(0) 0 0
Incrementable(0) Incrementable(0) 1 0
创建新对象时,该新对象尚未分配到incr()
。 Java(或Scala)中没有现有的机制允许我们使用与i
完全相同的语义来实现此方法。
现在,这并不意味着Scala不可能做出可能这样的事情。如果Scala支持通过引用传递参数(参见this wikipedia article中的“按引用调用”),就像C ++一样,那么我们可以实现它!
这是一个虚构的实现,假设与C ++中的相同的引用符号。
++
这需要JVM支持或Scala编译器上的一些严格的机制。
事实上,Scala 确实类似于创建闭包时所需要的东西 - 其中一个后果是原始的implicit def toIncr(Int &n) = {
def ++ = { val tmp = n; n += 1; tmp }
def prefix_++ = { n += 1; n }
}
变成了盒子,可能性能很差影响。
例如,请考虑以下方法:
Int
传递给 def f(l: List[Int]): Int = {
var sum = 0
l foreach { n => sum += n }
sum
}
,foreach
的代码不是此方法的一部分。方法{ n => sum += n }
采用foreach
类型的对象,Function1
方法实现了这个小代码。这意味着apply
不仅仅是在一个不同的方法上,而是在一个完全不同的类上!然而,它可以像{ n => sum += n }
运算符一样改变sum
的值。
如果我们使用++
来查看它,我们会看到:
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
局部变量,而是在堆上创建一个int
(在0),这就是IntRef
。真正的int
位于int
内部,正如我们在25中看到的那样。让我们看一下使用while循环实现同样的事情,以明确差异:
IntRef.elem
那变为:
def f(l: List[Int]): Int = {
var sum = 0
var next = l
while (next.nonEmpty) {
sum += next.head
next = next.tail
}
sum
}
上面没有对象创建,不需要从堆中获取内容。
因此,总而言之,Scala需要额外的功能来支持可由用户定义的增量运算符,因为它避免了为外部库提供其自己的内置类功能。一个这样的功能是通过引用传递参数,但JVM不提供它的支持。 Scala执行类似于调用by-reference的操作,并且这样做会使用装箱,这会严重影响性能(最有可能产生增量运算符的东西!)。因此,在没有JVM支持的情况下,这不太可能。
作为补充说明,Scala具有明显的功能倾向,具有可变性和参考透明度,具有可变性和副作用。调用by-reference的唯一目的是在调用者身上引起副作用 !虽然这样做可以在许多情况下带来性能优势,但它非常违背Scala的内容,因此我怀疑by-reference将成为其中的一部分。
答案 4 :(得分:9)
其他答案已经正确地指出++
运算符在函数式编程语言中既不是特别有用也不可取。我想补充一点,因为Scala 2.10,如果你愿意,你可以添加一个++
运算符。方法如下:
您需要一个隐式宏,将int转换为具有++
方法的实例。 ++
方法由宏“写入”,它可以访问调用++
方法的变量(而不是其值)。这是宏实现:
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
}
})
}
现在,只要隐式转换宏在范围内,您就可以编写
var i = 0
println(i++) //prints 0
println(i) //prints 1
答案 5 :(得分:7)
Rafe的回答是关于为什么像i ++这样的东西不属于Scala的理由。但是我有一个挑剔。实际上,如果不改变语言,就不可能在Scala中实现i ++。
在Scala中,++是一种有效的方法,没有方法意味着赋值。只有=
可以做到这一点。
C ++和Java等语言特别将++
视为增量和赋值。 Scala特别以不一致的方式对待=
。
在Scala中编写i += 1
时,编译器首先在Int上查找名为+=
的方法。它不存在,所以接下来它会在=
上发挥作用,并尝试编译该行,就像它读取i = i + 1
一样。如果你写i++
,那么Scala会调用++
上的方法i
,并将结果分配给......什么都没有。因为只有=
表示分配。你可以写i ++= 1
,但那种方法会失败。
Scala支持像+=
这样的方法名称的事实已经引起争议,有些人认为它是运算符重载。他们本可以为++
添加特殊行为,但它不再是一个有效的方法名称(如=
),这将是另一件需要记住的事情。
答案 6 :(得分:2)
相当多的语言不支持++符号,例如Lua。在支持它的语言中,它经常是混乱和错误的来源,因此它作为语言功能的质量是可疑的,并且与i += 1
或甚至i = i + 1
的替代方案相比,节省这些小角色是毫无意义的。
这与该语言的类型系统完全无关。虽然大多数静态类型语言确实提供了大多数静态类型语言,但大多数动态类型都不提供,这是一种相关性,绝对不是原因。
答案 7 :(得分:2)
Scala鼓励使用FP样式,i++
当然不是。
答案 8 :(得分:1)
要问的问题是为什么应该有这样的操作员,而不是为什么不应该这样。 Scala会被它改进吗?
++
运算符是单一用途的,并且具有可以更改变量值的运算符可能会导致问题。编写令人困惑的表达式很容易,即使语言定义了i = i + i++
的含义,例如,要记住的是很多详细的规则。
顺便说一下,你对Python和Ruby的推理是错误的。在Perl中,您可以写$i++
或++$i
就好了。如果$i
证明是无法递增的,则会出现运行时错误。它不是Python或Ruby,因为语言设计者认为这不是一个好主意,不是因为它们像Perl一样动态输入。
答案 9 :(得分:0)
你可以模拟它。作为一个简单的例子:
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)
添加一些隐式转化,你很高兴。但是,这种问题会变成:为什么不存在具有此功能的可变RichInt?
答案 10 :(得分:0)
根据another answer的建议,在i++
中发现的增量运算符为-
据说……是肯·汤普森(Ken Thompson)添加到B语言(C语言的前身)的原因,特别是因为[一旦编译,它就能够直接翻译成单个操作码
并不一定,因为这样的运算符与一般的加法和减法一样有用。尽管某些面向对象的语言(例如Java和C#)也具有增量运算符(通常是从C借用的),但并非所有人都可以(例如Ruby)。