Почему нет i ++ в Scala? - PullRequest
       2

Почему нет i ++ в Scala?

43 голосов
/ 23 декабря 2010

Мне просто интересно, почему нет i++ для увеличения числа. Как я знаю, языки вроде Ruby или Python не поддерживают его, потому что они динамически типизированы. Таким образом, очевидно, что мы не можем написать код, подобный i++, потому что, возможно, i - это строка или что-то еще. Но Scala статически типизирован - компилятор может сделать вывод, что если это допустимо или нет, ставить ++ за переменной.

Итак, почему i++ не существует в Scala?

Ответы [ 11 ]

70 голосов
/ 23 декабря 2010

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 - это просто методы.

35 голосов
/ 23 декабря 2010

Конечно, вы можете иметь это в 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

Нет необходимости прибегать к насилию над переменными.

14 голосов
/ 23 декабря 2010

Scala идеально подходит для анализа i++ и, с небольшой модификацией языка, может быть изменена переменная.Но на это есть множество причин.

Во-первых, он сохраняет только один символ, i++ против i+=1, что не очень экономит для добавления новой языковой функции.

Во-вторых, оператор ++ широко используется в библиотеке коллекций, где xs ++ ys берет коллекцию xs и ys и создает новую коллекцию, содержащую оба.

В-третьих, Scala пытаетсяпризываю вас, не заставляя вас, писать код функциональным образом.i++ является изменяемой операцией, поэтому она несовместима с идеей Scala, чтобы сделать ее особенно легкой.(Аналогично с языковой функцией, которая позволила бы ++ изменять переменную.)

11 голосов
/ 13 февраля 2013

В 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, поэтому я сомневаюсь, что вызов по ссылке будет когда-либо частью этого.

9 голосов
/ 14 февраля 2013

В других ответах уже правильно указано, что оператор ++ не особенно полезен и не желателен в функциональном языке программирования. Я хотел бы добавить, что начиная с Scala 2.10, вы можете добавить оператор ++, если хотите. Вот как:

Вам нужен неявный макрос, который преобразует целые числа в экземпляры чего-либо, имеющего метод ++. Метод ++ «записывается» макросом, который имеет доступ к переменной (в отличие от ее значения), для которой вызывается метод ++. Вот реализация макроса:

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
7 голосов
/ 23 декабря 2010

Ответ Рэйфа верен в отношении того, почему что-то вроде i ++ не принадлежит Scala.Однако у меня есть один придира.На самом деле невозможно реализовать i ++ в Scala без изменения языка.

В Scala ++ является допустимым методом, и ни один метод не подразумевает присваивания.Только = может сделать это.

Языки, такие как C ++ и Java, обрабатывают ++ специально для обозначения как приращения, так и присваивания.Scala обрабатывает = специально и непоследовательно.

В Scala, когда вы пишете i += 1, компилятор сначала ищет метод с именем += в Int.Его там нет, поэтому в следующий раз он делает магию на = и пытается скомпилировать строку, как если бы она читала i = i + 1.Если вы напишите i++, тогда Scala вызовет метод ++ для i и присвоит результат ... ничему.Потому что только = означает присваивание.Вы могли бы написать i ++= 1, но это противоречит цели.

Тот факт, что Scala поддерживает имена методов, такие как +=, уже противоречив, и некоторые считают, что это перегрузка операторов.Они могли бы добавить специальное поведение для ++, но тогда это больше не будет действительным именем метода (например, =), и это было бы еще одна вещь, которую нужно запомнить.

2 голосов
/ 23 декабря 2010

Scala поощряет использование стиля FP, который i++ определенно не является.

2 голосов
/ 23 декабря 2010

Многие языки не поддерживают нотацию ++, такую ​​как Lua. На языках, на которых он поддерживается, он часто является источником путаницы и ошибок, поэтому его качество как языковой особенности сомнительно, и по сравнению с альтернативой i += 1 или даже просто i = i + 1, сохранение таких второстепенных символов довольно бессмысленно.

Это совсем не относится к системе типов языка. Несмотря на то, что большинство языков статических типов действительно предлагают, а большинство динамических типов - нет, это корреляция и определенно не причина.

1 голос
/ 23 декабря 2010

Вопрос, который нужно задать, - почему должен быть такой оператор, а не почему его не должно быть. Будет ли это улучшено Scala?

Оператор ++ является одноцелевым, и наличие оператора, который может изменять значение переменной, может вызвать проблемы. Запутанные выражения легко написать, и даже если язык определяет, например, что означает i = i + i++, это множество подробных правил, которые нужно запомнить.

Кстати, ваши рассуждения о Python и Ruby неверны. В Perl вы можете написать $i++ или ++$i просто отлично. Если $i окажется чем-то, что не может быть увеличено, вы получите ошибку во время выполнения. Это не в Python или Ruby, потому что разработчики языка не думали, что это хорошая идея, а не потому, что они динамически набираются как Perl.

0 голосов
/ 23 ноября 2018

Поскольку другой ответ предполагает, что оператор приращения, найденный в i++, был & mdash;

предположительно ... добавлено к языку B [предшественнику языка C] Кеном Томпсоном специально, потому что [он был] способен переводить непосредственно в один код операции после компиляции

и не обязательно, потому что такой оператор так же полезен, как, скажем, общее сложение и вычитание. Хотя некоторые объектно-ориентированные языки (такие как Java и C #) также имеют оператор приращения (часто заимствованный из C), не все имеют (например, Ruby).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...