为什么Scala中没有i ++?

xia*_*owl 43 scala

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

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

Raf*_*ler 71

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

此外,i++由于它提供的控制流结构,Scala并不真正需要.在Java和其他人中,您i++经常需要构造whilefor循环遍历数组.但是,在Scala中,您可以说出您的意思:for(x <- someArray)或者说是someArray.foreach这些内容.i++在命令式编程中很有用,但是当你达到更高级别时,很少需要(在Python中,我从未发现自己需要它一次).

你在那个地方++ 可能是在Scala中,但它不是因为它是没有必要的,而且只会堵塞的语法.如果你真的需要它,比如说i += 1,但是因为Scala需要更多地使用不可变量和丰富的控制流来编程,你应该很少需要.你当然可以自己定义它,因为运算符确实只是Scala中的方法.

  • 好吧,`++`可能_not_在Scala中没有一些大的改动,因为它为一个标识符分配了一个新的值.从方法内部看不到此标识符,并且Scala没有传递引用来处理它(JVM也不是). (8认同)
  • 好吧,让我改进我的答案,然后:如果这是真的,那么Scala不允许你宣布vars.底线是Scala支持功能和OO,这是一个设计目标.所以说不支持i ++,因为Scala功能上没有意义. (2认同)
  • 丹尼尔指出了我的观点:我在Scala中不存在完全不同的原因. (2认同)

Apo*_*isp 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
Run Code Online (Sandbox Code Playgroud)

在这里你去:

scala> runProgram
res26: scalaz.Id.Id[Int] = 3
Run Code Online (Sandbox Code Playgroud)

无需对变量采取暴力行为.

  • 绝对令人惊讶Scala如何被滥用来做这样的事情:-) (10认同)
  • 实际上这有++ i的语义,而不是i ++.前一种语法在Scala中更难实现. (8认同)

Rex*_*err 14

Scala完全能够解析i++,并且只需对语言进行少量修改即可修改变量.但是有很多原因没有.

首先,它仅保存一个字符,i++i+=1,这是不是很节省添加新的语言功能.

其次,++运营商被广泛应用于集合库,其中xs ++ ys需要收集xsys并产生包含一个新的集合.

第三,Scala试图鼓励你,而不是强迫你,以功能的方式编写代码. i++是一个可变的操作,所以它与Scala的想法不一致,使它变得特别容易.(同样,语言功能允许++变异变量.)

  • `i + = 1`计算`()`,所以它不是直接的替代品.`{i + = 1; i}`将是.但我同意你的其余答案. (4认同)

Dan*_*ral 11

Scala没有++运算符,因为它不可能实现运算符.

编辑:正如刚才回答的那样,Scala 2.10.0 可以通过使用宏实现增量运算符.有关详细信息,请参阅此答案,并将以下所有内容视为Scala 2.10.0之前的版本.

让我详细说明一下,我将严重依赖Java,因为它实际上遇到了同样的问题,但如果我使用Java示例,人们可能更容易理解它.

首先,重要的是要注意Scala的目标之一是"内置"类不能具有库不能复制的任何功能.当然,在Scala中,a Int是一个类,而在Java中,a int是一个原语 - 一种完全不同于类的类型.

所以,斯卡拉支持i++i类型Int,我应该能够创建自己的类MyInt也支持同样的方法.这是Scala的驱动设计目标之一.

现在,Java自然不支持符号作为方法名称,所以我们只需要调用它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+")";
    }
}
Run Code Online (Sandbox Code Playgroud)

我们可以用这个来测试它:

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

一切似乎也有效:

Incrementable(0)
Incrementable(1)
Run Code Online (Sandbox Code Playgroud)

而且,现在,我将展示问题所在.让我们改变我们的演示程序,并把它比Incrementableint:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

正如我们在输出中看到的那样,Incrementable并且int表现不同:

i                   j                       k       l
Incrementable(0)    Incrementable(0)        0       0
Incrementable(1)    Incrementable(1)        1       0
Run Code Online (Sandbox Code Playgroud)

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

public Incrementable incr() {
    return new Incrementable(n + 1);
}
Run Code Online (Sandbox Code Playgroud)

但是,这不起作用:

i                   j                       k       l
Incrementable(0)    Incrementable(0)        0       0
Incrementable(0)    Incrementable(0)        1       0
Run Code Online (Sandbox Code Playgroud)

问题是,虽然,incr() 创建一个新的对象,新对象还没有被分配i.Java或Scala中没有现有的机制允许我们使用与语法完全相同的语义来实现此方法++.

现在,这并不意味着这将是不可能的斯卡拉做这样的事情可能.如果Scala支持通过引用传递参数(参见本维基百科文章中的 "按引用调用" ),就像C++一样,那么我们就可以实现它!

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

implicit def toIncr(Int &n) = {
  def ++ = { val tmp = n; n += 1; tmp }
  def prefix_++ = { n += 1; n }
}
Run Code Online (Sandbox Code Playgroud)

这将需要JVM支持或Scala编译器上的一些严重机制.

事实上,Scala 了类似于创建闭包时所需要的东西 - 其中一个后果是原件Int变成盒装,可能会对性能产生严重影响.

例如,考虑这种方法:

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

该代码被传递到foreach,{ n => sum += n }不是该方法的一部分.该方法foreach采用类型的对象,Function1apply方法实现了很少的代码.这意味着{ 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
Run Code Online (Sandbox Code Playgroud)

请注意,它不是创建一个int局部变量,而是IntRef在堆上创建一个(在0处),这就是装箱int.正如我们在25中看到的那样,真实int就在里面IntRef.elem.让我们看一下使用while循环实现同样的事情来明确区别:

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

那就变成:

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
Run Code Online (Sandbox Code Playgroud)

上面没有对象创建,不需要从堆中获取内容.

因此,总而言之,Scala需要额外的功能来支持可由用户定义的增量运算符,因为它避免了为外部库提供其自己的内置类功能.一个这样的功能是通过引用传递参数,但JVM不提供它的支持.Scala执行类似于调用by-reference的操作,并且这样做会使用装箱,这会严重影响性能(最有可能产生增量运算符的东西!).因此,在没有JVM支持的情况下,这种情况不太可能发生.

另外需要注意的是,Scala具有明显的功能性倾向,具有可变性和参考透明度,具有可变性和副作用.通过引用调用的唯一目的是对呼叫者造成副作用!虽然这样做可以在许多情况下带来性能优势,但它非常反对Scala,所以我怀疑by-reference将成为它的一部分.


Kim*_*bel 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
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

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

var i = 0
println(i++) //prints 0
println(i) //prints 1
Run Code Online (Sandbox Code Playgroud)


Cra*_*lin 7

Rafe的回答是关于为什么像i ++这样的东西不属于Scala的理由.但是我有一个挑剔.实际上,在不改变语言的情况下在Scala中实现i ++是不可能的.

在Scala中,++是一种有效的方法,没有方法意味着赋值.只能=这样做.

像C++和Java这样的语言++特别指的是增量和赋值.Scala =特别对待,并且以不一致的方式对待.

在编写Scala时i += 1,编译器首先查找+=在Int上调用的方法.它不存在,所以接下来就是它的神奇之处=并尝试编译该行,就像它读取一样i = i + 1.如果你写i++那么斯卡拉将调用方法++i和结果分配给什么都没有.因为只=意味着分配.你可以写,i ++= 1但那种方式会失败.

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