it-swarm.com.ru

Как вы можете сделать что-нибудь полезное без изменяемого состояния?

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

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

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

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

237
Mason Wheeler

Или, если вы играете в видеоигру, существует множество переменных состояния, начиная с позиций всех персонажей, которые имеют тенденцию постоянно перемещаться. Как вы можете сделать что-нибудь полезное, не отслеживая изменения значений?

Если вам интересно, здесь серия статей, которые описывают программирование игры с Erlang.

Вам, вероятно, не понравится этот ответ, но вам не понравится get функциональная программа, пока вы ее не используете. Я могу опубликовать примеры кода и сказать: "Вот, не так ли увидеть"- но если вы не понимаете синтаксис и основополагающие принципы, то ваши глаза просто глазурят. С вашей точки зрения, похоже, что я делаю то же самое, что и императивный язык, но просто настраиваю все виды границ, чтобы целенаправленно усложнять программирование. Моя точка зрения, вы просто испытываете парадокс Blub .

Сначала я был настроен скептически, но несколько лет назад я запрыгнул на поезд по функциональному программированию и влюбился в него. Уловка с функциональным программированием заключается в способности распознавать шаблоны, конкретные назначения переменных и перемещать императивное состояние в стек. Например, цикл for становится рекурсией:

// Imperative
let printTo x =
    for a in 1 .. x do
        printfn "%i" a

// Recursive
let printTo x =
    let rec loop a = if a <= x then printfn "%i" a; loop (a + 1)
    loop 1

Это не очень красиво, но мы получили тот же эффект без мутаций. Конечно, везде, где это возможно, мы предпочитаем избегать зацикливания и просто абстрагировать его:

// Preferred
let printTo x = seq { 1 .. x } |> Seq.iter (fun a -> printfn "%i" a)

Метод Seq.iter будет перечислять коллекцию и вызывать анонимную функцию для каждого элемента. Очень кстати :)

Я знаю, печать цифр не очень впечатляет. Однако мы можем использовать тот же подход с играми: сохранить все состояния в стеке и создать новый объект с нашими изменениями в рекурсивном вызове. Таким образом, каждый кадр является снимком состояния без игры, где каждый кадр просто создает новый объект с желаемыми изменениями любых объектов без состояния, которые необходимо обновить. Псевдокод для этого может быть:

// imperative version
pacman = new pacman(0, 0)
while true
    if key = UP then pacman.y++
    Elif key = DOWN then pacman.y--
    Elif key = LEFT then pacman.x--
    Elif key = UP then pacman.x++
    render(pacman)

// functional version
let rec loop pacman =
    render(pacman)
    let x, y = switch(key)
        case LEFT: pacman.x - 1, pacman.y
        case RIGHT: pacman.x + 1, pacman.y
        case UP: pacman.x, pacman.y - 1
        case DOWN: pacman.x, pacman.y + 1
    loop(new pacman(x, y))

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

Это масштабируется до любого количества объектов в игре, потому что все объекты (или коллекции связанных объектов) могут быть представлены в их собственном потоке.

Почти каждое пользовательское приложение, которое я могу представить, включает в себя состояние как основную концепцию.

В функциональных языках вместо того, чтобы изменять состояние объектов, мы просто возвращаем новый объект с теми изменениями, которые нам нужны. Это более эффективно, чем кажется. Например, структуры данных очень легко представить как неизменные структуры данных. Стеки, например, очень легко реализовать:

using System;

namespace ConsoleApplication1
{
    static class Stack
    {
        public static Stack<T> Cons<T>(T hd, Stack<T> tl) { return new Stack<T>(hd, tl); }
        public static Stack<T> Append<T>(Stack<T> x, Stack<T> y)
        {
            return x == null ? y : Cons(x.Head, Append(x.Tail, y));
        }
        public static void Iter<T>(Stack<T> x, Action<T> f) { if (x != null) { f(x.Head); Iter(x.Tail, f); } }
    }

    class Stack<T>
    {
        public readonly T Head;
        public readonly Stack<T> Tail;
        public Stack(T hd, Stack<T> tl)
        {
            this.Head = hd;
            this.Tail = tl;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Stack<int> x = Stack.Cons(1, Stack.Cons(2, Stack.Cons(3, Stack.Cons(4, null))));
            Stack<int> y = Stack.Cons(5, Stack.Cons(6, Stack.Cons(7, Stack.Cons(8, null))));
            Stack<int> z = Stack.Append(x, y);
            Stack.Iter(z, a => Console.WriteLine(a));
            Console.ReadKey(true);
        }
    }
}

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

type 'a stack =
    | Cons of 'a * 'a stack
    | Nil

let rec append x y =
    match x with
    | Cons(hd, tl) -> Cons(hd, append tl y)
    | Nil -> y

let rec iter f = function
    | Cons(hd, tl) -> f(hd); iter f tl
    | Nil -> ()

let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
let y = Cons(5, Cons(6, Cons(7, Cons(8, Nil))))
let z = append x y
iter (fun a -> printfn "%i" a) z

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

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

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

146
Juliet

Краткий ответ: вы не можете.

Так в чем же суть неизменности?

Если вы хорошо разбираетесь в императивном языке, то вы знаете, что "глобалы плохие". Зачем? Потому что они вводят (или имеют потенциал для внедрения) некоторые очень трудно распутываемые зависимости в вашем коде. И зависимости не хороши; Вы хотите, чтобы ваш код был модульный. Части программы не влияют на другие части как можно меньше. И FP приводит вас к святому Граалю модульности: никаких побочных эффектов вообще. У вас просто есть f(x) = y. Положи х, получи у. Без изменений х или чего-либо еще. FP заставляет вас перестать думать о состоянии и начать думать с точки зрения ценностей. Все ваши функции просто получают значения и производят новые значения.

Это имеет несколько преимуществ.

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

Во-вторых, это делает программу тривиально распараллеливаемой (другое дело - эффективное распараллеливание).

В-третьих, есть несколько возможных преимуществ производительности. Скажем, у вас есть функция:

double x = 2 * x

Теперь вы вводите значение 3, а вы получаете значение 6. Каждый раз. Но вы можете сделать это в обязательном порядке, верно? Ага. Но проблема в том, что в императиве вы можете сделать даже больше. Я могу сделать:

int y = 2;
int double(x){ return x * y; }

но я мог бы также сделать

int y = 2;
int double(x){ return x * (y++); }

Императивный компилятор не знает, будут ли у меня побочные эффекты или нет, что делает его более трудным для оптимизации (то есть double 2 не обязательно должно быть 4 каждый раз). Функциональный знает, что я не буду - следовательно, он может оптимизировать каждый раз, когда он видит "двойной 2".

Теперь, хотя создание новых значений каждый раз кажется невероятно расточительным для сложных типов значений с точки зрения памяти компьютера, это не обязательно должно быть так. Потому что, если у вас есть f(x) = y, а значения x и y "в основном одинаковы" (например, деревья, которые отличаются только несколькими листами), то x и y могут совместно использовать части памяти - потому что ни один из них не будет мутировать.

Так что, если эта неизменяемая вещь настолько хороша, почему я ответил, что без изменяемого состояния вы не сможете сделать ничего полезного. Что ж, без изменчивости вся ваша программа была бы гигантской функцией f(x) = y. И то же самое относится ко всем частям вашей программы: только к функциям и функциям в "чистом" смысле. Как я уже сказал, это означает f(x) = y каждые время. Так, например readFile ("myFile.txt") должен будет возвращать одно и то же строковое значение каждый раз. Не слишком полезно.

Следовательно, каждый FP предоставляет некоторые средства изменения состояния. "Чистые" функциональные языки (например, Haskell) делают это, используя несколько пугающие концепции, такие как монады, в то время как "нечистые" (например, ML) позволяют это напрямую.

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

73
oggy

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

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

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

Приложение: (Относительно вашего редактирования того, как отслеживать значения, которые нужно изменить)
Они будут храниться в неизменной структуре данных, конечно ...

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

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

25
jerryjvl

Я думаю, что есть небольшое недоразумение. Чисто функциональные программы имеют гос. Разница в том, как моделируется это состояние. В чисто функциональном программировании состоянием управляют функции, которые принимают некоторое состояние и возвращают следующее состояние. Последовательность через состояния затем достигается путем передачи состояния через последовательность чистых функций.

Таким образом можно смоделировать даже глобальное изменяемое состояние. Например, в Haskell программа - это функция от мира к миру. То есть вы передаете всю вселенную, и программа возвращает новую вселенную. Однако на практике вам нужно передать только те части вселенной, которые действительно интересуют вашу программу. И программы фактически возвращают последовательность действий, которые служат в качестве инструкций для операционной среды, в которой работает программа.

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

Рассмотрим этот код:

int x = 1;
int y = x + 1;
x = x + y;
return x;

Довольно болотный стандартный императивный код. Ничего интересного не делает, но это нормально для иллюстрации. Я думаю, вы согласитесь, что здесь участвует государство. Значение переменной x меняется со временем. Теперь давайте немного изменим обозначение, придумав новый синтаксис:

let x = 1 in
let y = x + 1 in
let z = x + y in z 

Поставьте скобки, чтобы было понятнее, что это значит:

let x = 1 in (let y = x + 1 in (let z = x + y in (z)))

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

Вы обнаружите, что этот шаблон может моделировать любое состояние, даже IO.

15
Apocalisp

Вот как вы пишете код без изменяемого состояния : вместо того, чтобы помещать изменяемое состояние в изменяемые переменные, вы помещаете его в параметры функций. И вместо того, чтобы писать циклы, вы пишете рекурсивные функции. Так, например, этот императивный код:

f_imperative(y) {
  local x;
  x := e;
  while p(x, y) do
    x := g(x, y)
  return h(x, y)
}

становится этим функциональным кодом (Схемоподобный синтаксис):

(define (f-functional y) 
  (letrec (
     (f-helper (lambda (x y)
                  (if (p x y) 
                     (f-helper (g x y) y)
                     (h x y)))))
     (f-helper e y)))

или этот гаскельский код

f_fun y = h x_final y
   where x_initial = e
         x_final   = loop x_initial
         loop x = if p x y then loop (g x y) else x

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

Вы можете найти хороший учебник с множеством примеров в статье Джона Хьюза Почему функциональное программирование имеет значение .

11
Norman Ramsey

Это просто разные способы сделать одно и то же.

Рассмотрим простой пример, такой как добавление чисел 3, 5 и 10. Представьте себе, что вы думаете об этом, сначала изменив значение 3, добавив к нему 5, затем добавьте 10 к этому "3", а затем выведите текущее значение " 3 "(18). Это кажется явно нелепым, но это, по сути, то, как часто делается основанное на состоянии императивное программирование. Действительно, у вас может быть много разных "3", которые имеют значение 3, но разные. Все это кажется странным, потому что мы так глубоко укоренились в весьма разумной идее, что числа неизменны.

Теперь подумайте о добавлении 3, 5 и 10, когда вы принимаете значения неизменяемыми. Вы добавляете 3 и 5 для получения другого значения 8, затем добавляете 10 к этому значению для получения еще одного значения 18.

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

10
Wedge

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

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

Первый императивный путь (в псевдокоде)

moveTo(dest, cur):
    while (cur != dest):
         if (cur < dest):
             cur += 1
         else:
             cur -= 1
    return cur

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

predicate ? if-true-expression : if-false-expression

Вы можете связать троичное выражение, поместив новое троичное выражение вместо ложного выражения.

predicate1 ? if-true1-expression :
predicate2 ? if-true2-expression :
else-expression

Итак, имея в виду, вот функциональная версия.

moveTo(dest, cur):
    return (
        cur == dest ? return cur :
        cur < dest ? moveTo(dest, cur + 1) : 
        moveTo(dest, cur - 1)
    )

Это тривиальный пример. Если бы это перемещало людей в игровом мире, вам пришлось бы вводить побочные эффекты, такие как отображение текущей позиции объекта на экране и вводить небольшую задержку в каждом вызове в зависимости от скорости движения объекта. Но вам все еще не нужно изменяемое состояние.

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

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

Я рекомендую сделать Little Schemer (обратите внимание, что я говорю "делать", а не "читать"), а затем выполнять все упражнения в SICP. Когда вы закончите, у вас будет другой мозг, чем когда вы начали.

7
Just Another Justin

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

6
Jherico

На самом деле довольно легко иметь нечто, похожее на изменяемое состояние, даже в языках без изменяемого состояния.

Рассмотрим функцию с типом s -> (a, s). В переводе с синтаксиса Haskell это означает, что функция принимает один параметр типа "s" и возвращает пару значений типов "a" и "s". Если s является типом нашего состояния, эта функция принимает одно состояние и возвращает новое состояние и, возможно, значение (вы всегда можете вернуть "unit", также известный как (), который в некотором роде эквивалентен "void" в C/C++, как тип "a"). Если вы объединяете несколько вызовов функций с такими типами (получение состояния, возвращаемого из одной функции и передача его следующей), вы получаете "изменяемое" состояние (фактически вы находитесь в каждой функции, создавая новое состояние и отказываясь от старого. ).

Может быть легче понять, если вы представляете изменяемое состояние как "пространство", в котором выполняется ваша программа, а затем думаете о временном измерении. В момент времени t1 "пространство" находится в определенном состоянии (скажем, например, некоторая ячейка памяти имеет значение 5). В более поздний момент времени t2 он находится в другом состоянии (например, ячейка памяти теперь имеет значение 10). Каждый из этих временных "кусочков" является состоянием, и он неизменен (вы не можете вернуться во времени, чтобы изменить их). Итак, с этой точки зрения, вы перешли от полного пространства-времени со стрелкой времени (ваше изменяемое состояние) к набору кусочков пространства-времени (несколько неизменяемых состояний), и ваша программа просто обрабатывает каждый кусочек как значение и вычисляет каждый из них как функция, примененная к предыдущему.

ОК, может быть, это было не легче понять :-)

Может показаться неэффективным явное представление всего состояния программы как значения, которое должно быть создано только для того, чтобы отбрасывать его в следующий момент (сразу после создания нового). Для некоторых алгоритмов это может быть естественным, но когда это не так, есть другой прием. Вместо реального состояния вы можете использовать ложное состояние, которое является не чем иным, как маркером (давайте назовем тип этого ложного состояния State#). Это поддельное состояние существует с точки зрения языка и передается как любое другое значение, но компилятор полностью его пропускает при генерации машинного кода. Он служит только для обозначения последовательности выполнения.

В качестве примера предположим, что компилятор предоставляет нам следующие функции:

readRef :: Ref a -> State# -> (a, State#)
writeRef :: Ref a -> a -> State# -> (a, State#)

Переводя из этих объявлений, подобных Haskell, readRef получает что-то, напоминающее указатель или дескриптор, к значению типа "a" и ложному состоянию, и возвращает значение типа "a", на которое указывает первый параметр, и новый поддельное состояние. writeRef похож, но изменяет значение, на которое указывает.

Если вы вызываете readRef и затем передаете ему ложное состояние, возвращаемое writeRef (возможно, с другими вызовами несвязанных функций в середине; эти значения состояния создают "цепочку" вызовов функций), он вернет записанное значение. Вы можете снова вызвать writeRef с тем же указателем/дескриптором, и он запишет в ту же ячейку памяти - но, поскольку концептуально он возвращает новое (поддельное) состояние, состояние (поддельное) все еще остается неизменным (новое было " создано "). Компилятор будет вызывать функции в том порядке, в котором он должен был бы вызывать их, если бы существовала переменная реального состояния, которую нужно было вычислить, но единственным состоянием, которое там было, является полное (изменяемое) состояние реального оборудования.

(Те, кто знает Haskell, заметят, что я многое упростил и пропустил несколько важных деталей. Для тех, кто хочет увидеть больше деталей, взгляните на Control.Monad.State из mtl и в ST s и IO (также известный как ST RealWorld).)

Вы можете спросить, зачем делать это окольным путем (вместо того, чтобы просто иметь изменяемое состояние в языке). Реальное преимущество в том, что у вас reified состояние вашей программы. То, что раньше было неявным (состояние вашей программы было глобальным, учитывая такие вещи, как действие на расстоянии ), теперь явное. Функции, которые не получают и не возвращают состояние, не могут изменять его или влиять на него; они "чисты". Более того, у вас могут быть отдельные потоки состояний, и с небольшим количеством магии типов они могут использоваться для встраивания императивных вычислений в чистые, не делая их нечистыми (монада ST в Haskell обычно используется для этого трюка. ; State#, о котором я упоминал выше, на самом деле является State# s от GHC, который используется реализацией монад ST и IO).

6
CesarB

В дополнение к отличным ответам, которые дают другие, подумайте о классах Integer и String в Java. Экземпляры этих классов являются неизменяемыми, но это не делает классы бесполезными только потому, что их экземпляры нельзя изменить. Неизменность дает вам некоторую безопасность. Вы знаете, что если вы используете экземпляр String или Integer в качестве ключа для Map, ключ не может быть изменен. Сравните это с классом Date в Java:

Date date = new Date();
mymap.put(date, date.toString());
// Some time later:
date.setTime(new Date().getTime());

Вы тихо изменили ключ на своей карте! Работа с неизменяемыми объектами, такими как функциональное программирование, намного чище. Проще понять, какие побочные эффекты возникают - нет! Это означает, что это проще для программиста, а также для оптимизатора.

4
Eddie

Для высокоинтерактивных приложений, таких как игры, функциональное реактивное программирование - ваш друг: если вы можете сформулировать свойства игрового мира как изменяющиеся во времени значения (и/или потоки событий), вы готовы! Эти формулы иногда будут даже более естественными и более интересными, чем изменение состояния, например для движущегося шара вы можете напрямую использовать известный закон x = v * t . И что еще лучше, правила игры, написанные таким образом , составляют лучше, чем объектно-ориентированные абстракции. Например, в этом случае скорость мяча может быть также изменяющейся во времени величиной, которая зависит от потока событий, состоящего из столкновений мяча. Для получения более подробной информации о дизайне см. Создание игр в Вязах .

3
thSoft
3
Paul Sweatte

Вот как FORTRAN будет работать без блоков COMMON: вы будете писать методы, в которых будут передаваться значения и локальные переменные. Вот и все.

Объектно-ориентированное программирование объединило нас в состояние и поведение, но это была новая идея, когда я впервые столкнулся с ней из C++ в 1994 году.

Боже, я был функциональным программистом, когда был инженером-механиком, и я этого не знал!

2
duffymo

Имейте в виду: функциональные языки Тьюринга полны. Поэтому любое полезное задание, которое вы выполняете на непостоянном языке, может быть выполнено на функциональном языке. В конце концов, я думаю, что можно сказать о гибридном подходе. Такие языки, как F # и Clojure (и я уверен, что другие) поощряют дизайн без сохранения состояния, но допускают изменчивость при необходимости.

1
Jason Baker

Вы не можете иметь чистый функциональный язык, который полезен. Всегда будет уровень изменчивости, с которым вам придется иметь дело, IO является одним из примеров.

Думайте о функциональных языках как о еще одном инструменте, который вы используете. Это хорошо для определенных вещей, но не для других. Пример игры, который вы привели, может быть не лучшим способом использования функционального языка, по крайней мере на экране будет изменчивое состояние, с которым вы ничего не можете сделать с помощью FP. То, как вы думаете о проблеме и тип проблем, которые вы решаете с помощью FP, будет отличаться от того, к которому вы привыкли в императивном программировании.

1
Up.
0
Spencer Ruport