В Scala нет оператора ++
, поскольку его невозможно реализовать в нем.
РЕДАКТИРОВАТЬ : Как только что указывалось в ответе на этот ответ, Scala 2.10.0 может реализовать оператор приращения с помощью макросов.См. этот ответ для получения подробной информации и примите все ниже, как до Scala 2.10.0.
Позвольте мне подробнее остановиться на этом, и я буду сильно полагаться на Java, поскольку на самом деле она страдает от той же проблемы, но людям, возможно, будет легче понять ее, если я использую пример Java.
Для начала важно отметить, что одной из целей Scala является то, что «встроенные» классы не должны иметь никаких возможностей, которые не могут быть продублированы библиотекой.И, конечно же, в Scala Int
является классом, тогда как в Java int
является примитивом - типом, совершенно отличным от класса.
Итак, для Scala поддерживается 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+")";
}
}
Мы можем проверитьэто с этим:
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
должен быть неизменным, что означает, что 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()
создал aновый объект, этот новый объект не был назначен на 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.
Фактически Scala делает что-то похожее на то, что было бы необходимо для создания замыканий, и одним из последствий этого является то, что исходный 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
она создает IntRef
в куче (в 0), которая упаковывает 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
Нет создания объектов выше, нет необходимости получать что-то из кучи.
Итак, в заключение, Scala потребуются дополнительные возможности для поддержки оператора приращения, который может быть определен пользователем, поскольку он избегает предоставления своих собственных встроенных возможностей классов, недоступных внешним библиотекам. Одной из таких возможностей является передача параметров по ссылке, но JVM не обеспечивает их поддержку. Scala делает что-то похожее на вызов по ссылке, и для этого используется бокс, который может серьезно повлиять на производительность (то, что, скорее всего, придумает оператор приращения!). Поэтому в отсутствие поддержки JVM это маловероятно.
Как дополнительное примечание, у Scala есть четкий функциональный уклон, обеспечивающий неизменность и прозрачность ссылок по сравнению с изменчивостью и побочными эффектами. единственная цель вызова по ссылке - вызвать побочные эффекты на вызывающем абоненте ! Несмотря на то, что это может принести преимущества в производительности в ряде ситуаций, оно очень сильно противоречит структуре Scala, поэтому я сомневаюсь, что вызов по ссылке будет когда-либо частью этого.