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

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

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

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

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

Ответ 1

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

Если вам интересно, вот серия статей, описывающих игровое программирование с Erlang.

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

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

// 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);
        }
    }
}

Приведенный выше код создает два неизменяемых списка, добавляет их вместе для создания нового списка и добавляет результаты. Никакое изменчивое состояние не используется в любом месте приложения. Он выглядит немного громоздким, но это только потому, что С# является подробным языком. Здесь эквивалентная программа в 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

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

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

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

Ответ 2

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

Итак, какова тогда суета о неизменности?

Если вы хорошо разбираетесь в императивном языке, тогда вы знаете, что "глобальные перемены плохие". Зачем? Потому что они внедряют (или могут вносить в них) некоторые очень трудно-распутывающие зависимости в вашем коде. И зависимости не очень хорошие; вы хотите, чтобы ваш код был модульным. Части программы не влияют на другие части как можно меньше. И FP приносит вам святой грааль модульности: никаких побочных эффектов вообще нет. У вас просто есть f (x) = y. Положите x в, получите y out. Никаких изменений в x или что-либо еще. 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++); }

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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)))

или этот код Haskellish

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

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

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

Ответ 5

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

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

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

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

Ответ 6

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

Даже глобальное изменяемое состояние может быть смоделировано таким образом. Например, в 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.

Ответ 7

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

Ответ 8

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

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

Сначала императивный способ (в псевдокоде)

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. Научиться думать рекурсивно не сложно, но это требует как практики, так и инструментария. Этот небольшой раздел в этой книге "Learn Java", где они использовали рекурсию для вычисления факториала, не сокращает ее. Вам нужен инструментарий для таких навыков, как создание итеративных процессов из рекурсии (вот почему хвостовая рекурсия важна для функционального языка), продолжения, инварианты и т.д. Вы бы не программировали OO, не узнав о модификаторах доступа, интерфейсах и т.д. То же самое для функционального программирования.

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

Ответ 9

В дополнение к отличным ответам, которые дают другие, подумайте о классах 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());

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

Ответ 10

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

Рассмотрим функцию с типом s -> (a, s). При переводе из синтаксиса Haskell это означает функцию, которая принимает один параметр типа "s" и возвращает пару значений типов "a" и "s". Если s является типом нашего состояния, эта функция принимает одно состояние и возвращает новое состояние и, возможно, значение (вы всегда можете вернуть "unit" aka (), что эквивалентно "void", в 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 (aka ST RealWorld)).

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

Ответ 11

Используя некоторое креативность и соответствие шаблонов, были созданы игры без сохранения:

а также демонстрационные ролики:

и визуализации:

Ответ 12

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

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

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

Ответ 13

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

Ответ 14

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

Ответ 15

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

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

Ответ 17

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

Вот пример:

function ReadDataFromKeyboard() {
    $input_values = $_POST[];
    return $input_values;
}
function ProcessInformation($input_values) {
    if ($input_values['a'] > 10)
        return ($input_values['a'] + $input_values['b'] + 3);
    else if ($input_values['a'] > 5)
        return ($input_values['b'] * 3);
    else
        return ($input_values['b'] - $input_values['a'] - 7);
}
function DisplayToPage($data) {
    print "Based your input, the answer is: ";
    print $data;
    print "\n";
}

/* begin: */
DisplayToPage (
    ProcessInformation (
        GetDataFromKeyboard()
    )
);