Считаете ли вы, что вам все еще нужны переменные, которые вы можете изменить, и если да, то почему? - PullRequest
33 голосов
/ 04 марта 2009

Один из аргументов, которые я слышал против функциональных языков, заключается в том, что кодирование с одним присваиванием является слишком сложным или, по крайней мере, значительно сложнее, чем "нормальное" программирование.

Но, просматривая мой код, я понял, что у меня действительно нет многих (каких-либо?) Шаблонов использования, которые нельзя написать так же хорошо, используя одну форму назначения, если вы пишете на достаточно современном языке.

Так, каковы варианты использования для переменных, которые изменяются в пределах одного вызова их области? Принимая во внимание, что индексы цикла, параметры и другие значения границ области, которые варьируются между вызовами, не являются множественными назначениями в этом случае (если только вам не нужно изменить их в теле для некоторых причина), и предполагая, что вы пишете что-то намного выше уровня ассемблера, где вы можете написать что-то вроде

values.sum

или (если сумма не указана)

function collection.sum --> inject(zero, function (v,t) --> t+v )

и

x = if a > b then a else b

или

n = case s 
  /^\d*$/ : s.to_int
  ''      : 0
  '*'     : a.length
  '?'     : a.length.random
  else    fail "I don't know how many you want"

когда вам нужно, и есть списки, карты / сбор и т. Д.

Считаете ли вы, что вам все еще нужны / нужны изменяемые переменные в такой среде, и если да, то для чего?

Чтобы уточнить, я не прошу пересказывать возражения против формы SSA, а скорее конкретные примеры, где эти возражения будут применяться. Я ищу фрагменты кода, которые ясны и лаконичны с изменяемыми переменными и не могут быть написаны без них.

Мои любимые примеры (и лучшее возражение, которое я ожидаю от них):

  1. Ответ Пола Джонсона Алгоритма Фишера-Йейтса , который довольно силен, когда вы включаете ограничения big-O. Но затем, как указывает catulahoops, проблема большого O связана не с вопросом о SSA, а с наличием изменяемых типов данных, и, если оставить это в стороне, алгоритм можно довольно четко записать в SSA:

     shuffle(Lst) ->
         array:to_list(shuffle(array:from_list(Lst), erlang:length(Lst) - 1)).
     shuffle(Array, 0) -> Array;
     shuffle(Array, N) ->
         K = random:uniform(N) - 1,
         Ek = array:get(K, Array),
         En = array:get(N, Array),
         shuffle(array:set(K, En, array:set(N, Ek, Array)), N-1).
    
  2. jpalecek's площадь многоугольника пример:

    def area(figure : List[Point]) : Float = {
      if(figure.empty) return 0
      val last = figure(0)
      var first= figure(0)
      val ret = 0
      for (pt <- figure) {
        ret+=crossprod(last - first, pt - first)
        last = pt
      }
      ret
    }
    

    который еще может быть записан примерно так:

    def area(figure : List[Point]) : Float = {
        if figure.length < 3
            0
          else
            var a = figure(0)
            var b = figure(1)
            var c = figure(2)
            if figure.length == 3
                magnitude(crossproduct(b-a,c-a))
              else 
                foldLeft((0,a,b))(figure.rest)) { 
                   ((t,a,b),c) => (t+area([a,b,c]),a,c)
                   }
    

    Или, так как некоторые люди возражают против плотности этой формулировки, это может быть изменено:

    def area([])    = 0.0   # An empty figure has no area
    def area([_])   = 0.0   # ...nor does a point
    def area([_,_]) = 0.0   # ...or a line segment
    def area([a,b,c]) =     # The area of a triangle can be found directly
        magnitude(crossproduct(b-a,c-a))
    def area(figure) =      # For larger figures, reduce to triangles and sum
        as_triangles(figure).collect(area).sum
    
    def as_triangles([])      = []  # No triangles without at least three points
    def as_triangles([_])     = []
    def as_triangles([_,_])   = []
    def as_triangles([a,b,c | rest) = [[a,b,c] | as_triangles([a,c | rest])]
    
  3. Идея принцессы о сложности реализации O (1) очередей с неизменяемыми структурами интересна (и вполне может послужить основой для убедительного примера), но, как уже говорилось, она в основном касается изменчивости структуры данных, а не непосредственно о проблеме множественного назначения.

  4. Я заинтригован ответом Сита Эратосфена, но не уверен. Правильное big-O, вытащить столько простых чисел, сколько нужно для генератора, приведенного в цитируемой им статье, нелегко реализовать правильно с или без SSA.


Ну, спасибо всем за попытку. Поскольку большинство ответов оказались либо 1) основанными на изменчивых структурах данных, а не на одном назначении, и 2) в той степени, в которой они касались формы единого назначения, легко встречаемой специалистами в данной области техники, я собираюсь вычеркните линию из моего выступления и / или реструктурируйте (возможно, сделайте это в резервной копии в качестве темы для обсуждения в маловероятном случае, когда у меня заканчиваются слова, прежде чем у меня заканчивается время).

Еще раз спасибо.

Ответы [ 14 ]

1 голос
/ 07 марта 2009

Локальные (методические) переменные, безусловно, никогда не имеют , которые должны быть назначены дважды. Но даже в функциональном программировании переназначение переменной допускается. Это изменение (части) значения, которое не допускается. И, как dsimcha уже ответил, для очень больших структур (возможно, в корне приложения) мне кажется невозможным заменить всю структуру. Думаю об этом. Состояние приложения в конечном счете определяется методом точки входа вашего приложения. Если абсолютно никакое состояние не может измениться без замены, вам придется перезапускать ваше приложение при каждом нажатии клавиши. (

0 голосов
/ 13 марта 2009

Я знаю, что вы просили код, который показал преимущества изменяемых переменных. И я хотел бы предоставить это. Но, как указывалось ранее, нет проблем, которые не могут быть выражены в обеих модах. И тем более, что вы указали, что область примера полигона jpalecek может быть написана с помощью свертываемого алгоритма (который, на мой взгляд, намного сложнее и поднимает проблему на другой уровень сложности), - это заставило меня задуматься, почему вы переходите на изменчивость жесткий. Поэтому я попытаюсь привести аргументы в пользу общей позиции и сосуществования неизменяемых и изменяемых данных.

По моему мнению, этот вопрос немного упускает из виду. Я знаю, что нам, программистам, нравятся вещи простые и понятные, но мы иногда упускаем из виду, что смесь тоже возможна. И, вероятно, поэтому в дискуссии об неизменности редко кто-то занимает среднюю позицию. Мне просто интересно, почему, потому что давайте посмотрим правде в глаза - неизменность является отличным инструментом абстрагирование всех видов проблем. Но иногда это огромная боль в заднице . Иногда это просто слишком сложно. И это само по себе заставляет меня остановиться и все такое - мы действительно хотим потерять изменчивость? Это действительно или-или? Нет ли какой-нибудь точки соприкосновения , к которой мы можем прийти? Когда неизменность помогает мне быстрее достичь своих целей, когда изменчивость? Какое решение легче читать и поддерживать? (Какой для меня самый большой вопрос)

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

Часто параллелизм вбрасывается в спор об неизменности. Я работал над проблемными наборами, для решения которых требовалось более 100 процессоров в разумные сроки. И это научило меня одной очень важной вещи: большую часть времени распараллеливание манипуляций с графиками данных на самом деле - не та вещь, которая будет наиболее эффективным способом распараллеливания. Это, безусловно, может принести большую пользу, но дисбаланс является реальной проблемой в этом проблемном пространстве. Поэтому обычно параллельная работа над несколькими изменяемыми графами и обмен информацией с неизменяемыми сообщениями намного эффективнее. Это означает, что когда я знаю, что график изолирован, что я не открыл его внешнему миру, я хотел бы выполнять свои операции над ним самым кратким образом, который я могу себе представить. И это обычно включает в себя изменение данных. Но после этих операций с данными я хочу открыть данные для всего мира - и в этот момент я обычно немного нервничаю, если данные изменчивы. Поскольку другие части программы могут повредить данные, состояние становится недействительным ... потому что после открытия миру данные часто попадают в мир параллелизма.

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

Это заставило меня прийти к простому выводу: настоящая проблема для меня заключается в том, что ни один из языков программирования, с которыми я знаком, позволяет мне сказать: «После этого вся эта структура данных должна быть неизменной» и "дайте мне изменяемую копию этой неизменяемой структуры данных здесь, пожалуйста, убедитесь, что только я могу видеть изменяемую копию" . Прямо сейчас я должен гарантировать это сам, щелкая бит readonly или что-то подобное. Если бы у нас могла быть поддержка компилятора, это не только гарантировало бы мне, что я не сделал ничего глупого после переключения указанного бита, но и могло фактически помочь компилятору выполнить различные оптимизации, которые он не мог сделать раньше. Плюс - язык все еще будет привлекательным для программистов, которые более знакомы с моделью императивного программирования.

Итак, подведем итоги. Программы IMHO обычно имеют веские основания использовать как неизменяемые, так и изменяемые представления графов данных . Я бы сказал, что данные должны быть неизменяемыми по умолчанию , и компилятор должен обеспечить это - но у нас должно быть понятие частных изменяемых представлений , потому что, естественно, существуют области, где многопоточность никогда не достигать - а удобочитаемость и удобство обслуживания могут выиграть от более обязательного структурирования.

0 голосов
/ 07 марта 2009

Я не особо задумывался об этом, за исключением того момента, когда вы на это указываете.

На самом деле я стараюсь не использовать несколько заданий подсознательно.

Вот пример того, о чем я говорю, в python

start = self.offset%n
if start:
    start = n-start

Написано таким образом, чтобы избежать ненужных дополнительных модуля или вычитания. Это используется с длинными типами Bignum, поэтому его стоит оптимизировать. Однако важно то, что это действительно одно задание.

Я бы совсем не пропустил многократное назначение.

0 голосов
/ 04 марта 2009

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

...