为什么Scala中没有i ++?

时间:2010-12-23 16:10:59

标签: scala

我只是想知道为什么没有i++来增加数字。据我所知,Ruby或Python等语言不支持它,因为它们是动态类型的。所以显然我们不能编写像i++这样的代码,因为i可能是一个字符串或其他东西。但是Scala是静态类型的 - 编译器绝对可以推断,如果将++放在变量后面是合法的还是合法的。

那么,为什么Scala中不存在i++

11 个答案:

答案 0 :(得分:70)

Scala没有i++,因为它是一种函数式语言,在函数式语言中,避免了带副作用的操作(在纯函数式语言中,根本不允许任何副作用)。 i++的副作用是i现在比之前大1。相反,您应该尝试使用不可变对象(例如val而非var)。

此外,Scala并不真正需要i++,因为它提供了控制流构造。在Java和其他人中,您经常需要i++来构造迭代数组的whilefor循环。但是,在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采用集合xsys并生成包含两者的新集合。

第三,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)。