Почему нет я ++ в Scala?
Мне просто интересно, почему нет i++
для увеличения числа. Как я знаю, такие языки, как Ruby или Python, не поддерживают его, потому что они динамически типизированы. Таким образом, очевидно, что мы не можем писать код типа i++
, потому что, возможно, i
является строкой или чем-то еще. Но Scala статически типизирован - компилятор абсолютно может сделать вывод, что если он легален или не помещает ++
за переменную.
Итак, почему не существует i++
в Scala?
Ответы
Ответ 1
Scala не имеет i++
, потому что это функциональный язык, а в функциональных языках избегают операций с побочными эффектами (на чисто функциональном языке вообще никаких побочных эффектов не разрешено). Побочным эффектом i++
является то, что i
теперь на 1 больше, чем раньше. Вместо этого вы должны попытаться использовать неизменяемые объекты (например, val
not var
).
Кроме того, Scala действительно не нуждается в i++
из-за создаваемых им конструкций потока управления. В Java и других случаях вам нужно i++
часто создавать контуры while
и for
, которые перебирают массивы. Однако в Scala вы можете просто сказать, что вы имеете в виду: for(x <- someArray)
или someArray.foreach
или что-то в этом направлении. i++
полезен в императивном программировании, но когда вы переходите на более высокий уровень, это редко необходимо (в Python я никогда не нуждался в нем один раз).
Вы заметили, что ++
может быть в Scala, но это не потому, что это не нужно, а просто забивает синтаксис. Если вам это действительно нужно, скажите i += 1
, но поскольку Scala чаще всего требует программирования с неизменяемыми и богатым потоком управления, вам редко приходится это делать. Вы, конечно, могли бы определить это самостоятельно, поскольку операторы действительно являются просто методами в Scala.
Ответ 2
Конечно, вы можете иметь это в 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
Не нужно прибегать к насилию против переменных.
Ответ 3
Scala отлично подходит для синтаксического анализа i++
и, с небольшой модификацией языка, может быть изменен для изменения переменной. Но есть множество причин не делать этого.
Во-первых, он сохраняет только один символ, i++
vs. i+=1
, что не очень удобно для добавления новой языковой функции.
Во-вторых, оператор ++
широко используется в библиотеке коллекций, где xs ++ ys
принимает коллекции xs
и ys
и создает новую коллекцию, содержащую оба.
В-третьих, Scala пытается побудить вас, не заставляя вас писать код функциональным способом. i++
является изменчивой операцией, поэтому она несовместима с идеей Scala, чтобы сделать ее особенно легкой. (Аналогично с языковой функцией, которая позволяет ++
мутировать переменную.)
Ответ 4
Scala не имеет оператора ++
, потому что его невозможно реализовать.
EDIT. Как было указано в ответ на этот ответ, Scala 2.10.0 может реализовать оператор инкремента с помощью макросов. Подробнее см. этот ответ и возьмите все ниже как pre- Scala 2.10.0.
Позвольте мне подробно остановиться на этом, и я буду сильно полагаться на Java, поскольку он действительно страдает от одной и той же проблемы, но людям может быть проще понять это, если я использую пример Java.
Чтобы начать, важно отметить, что одна из целей Scala заключается в том, что "встроенные" классы не должны иметь никаких возможностей, которые невозможно было бы дублировать библиотекой. И, конечно же, в Scala a Int
является классом, тогда как в Java an 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()
создал новый объект, этот новый объект не был назначен i
. Нет существующего механизма в Java - или Scala - это позволит нам реализовать этот метод с той же семантикой, что и ++
.
Теперь это не означает, что невозможно сделать Scala возможным. Если Scala поддерживается параметр, передаваемый по ссылке (см. "Вызов по ссылке" в эта статья в википедии), как это делает С++, тогда мы могли бы реализовать это!
Вот фиктивная реализация, предполагающая ту же нотацию, что и в С++.
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, поэтому я сомневаюсь, что ссылка на вызов будет когда-либо частью этого.
Ответ 5
Другие ответы уже правильно указали, что оператор ++
не является ни особенно полезным, ни желательным на языке функционального программирования. Я хотел бы добавить, что с 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
Ответ 6
Ответ Rafe верен относительно обоснования того, почему что-то вроде я ++ не принадлежит Scala. Однако у меня есть один нитпик. На самом деле невозможно реализовать я ++ в Scala без изменения языка.
В Scala, ++ является допустимым методом, и никакой метод не подразумевает назначение. Только =
может это сделать.
Языки, такие как С++ и Java, рассматривают ++
специально для обозначения как прироста, так и назначения. Scala рассматривает =
специально и непоследовательно.
В Scala при написании i += 1
компилятор сначала ищет метод под названием +=
в Int. Это не так, так что теперь это волшебство на =
и пытается скомпилировать строку так, как будто она читает i = i + 1
. Если вы напишете i++
, то Scala вызовет метод ++
на i
и присвойте результат... ничего. Потому что только =
означает назначение. Вы можете написать i ++= 1
, но этот вид поражения цели.
Тот факт, что Scala поддерживает имена методов, такие как +=
, уже вызывает противоречие, и некоторые люди считают его перегрузкой оператора. Они могли бы добавить специальное поведение для ++
, но тогда оно больше не будет корректным именем метода (например, =
), и это будет еще одна вещь, о которой нужно помнить.
Ответ 7
Довольно много языков не поддерживают обозначение ++, например Lua. В языках, на которых он поддерживается, он часто является источником путаницы и ошибок, поэтому качество как языковой функции сомнительно и сравнивается с альтернативой i += 1
или даже просто i = i + 1
, сохранение таких второстепенных символов довольно бессмысленно.
Это совсем не относится к системе типов языка. Хотя верно, что большинство языков статического типа предлагают, а большинство динамических типов этого не делают, это корреляция и определенно не причина.
Ответ 8
Scala рекомендует использовать стиль FP, который i++
, конечно, не является.
Ответ 9
Вопрос: почему должен быть такой оператор, а не почему этого не должно быть. Улучшится ли Scala?
Оператор ++
является одноцелевым и имеет оператор, который может изменить значение переменной, может вызвать проблемы. Легко писать запутанные выражения, и даже если язык определяет, что означает i = i + i++
, например, много подробных правил для запоминания.
Ваши рассуждения о Python и Ruby, кстати, ошибочны. В Perl вы можете просто написать $i++
или ++$i
. Если $i
оказывается чем-то, что не может быть увеличено, вы получите ошибку во время выполнения. Это не в Python или Ruby, потому что разработчики языка не думали, что это хорошая идея, а не потому, что они динамически набираются как Perl.
Ответ 10
Вы могли бы имитировать это. В качестве тривиального примера:
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 с этой функциональностью?