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

StackOverflow https://stackoverflow.com/questions/1020653

  •  06-07-2019
  •  | 
  •  

Вопрос

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

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

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

РЕДАКТИРОВАТЬ:Похоже, что куча ответов пытается убедить меня в преимуществах неизменяемых значений.Я понимаю эту часть.Это имеет смысл.Чего я не понимаю, так это того, как можно отслеживать значения, которые должны меняться, причем постоянно, без изменяемых переменных.

Это было полезно?

Решение

  

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

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

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

Сначала я был настроен скептически, но несколько лет назад я запрыгнул на поезд по функциональному программированию и влюбился в него. Уловка с функциональным программированием заключается в способности распознавать шаблоны, конкретные назначения переменных и перемещать императивное состояние в стек. Например, цикл 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

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

Другие советы

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

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

Если вы хорошо разбираетесь в императивном языке, то знаете, что "глобальные перемены плохие". Зачем? Потому что они вводят (или имеют потенциал для внедрения) некоторые очень трудно распутываемые зависимости в вашем коде. И зависимости не хороши; Вы хотите, чтобы ваш код был модульным . Части программы не влияют на другие части как можно меньше. И 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 предоставляет некоторые средства для изменения состояния. & Quot; Чистый & Quot; функциональные языки (например, Haskell) делают это, используя несколько пугающие понятия, такие как монады, в то время как «нечистый» те (например, ML) позволяют это напрямую.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    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" Книга, в которой они использовали рекурсию для вычисления факториала, не режет его. Вам необходим набор навыков, таких как создание итеративных процессов из рекурсии (вот почему хвостовая рекурсия необходима для функционального языка), продолжения, инварианты и т. Д. Вы не будете заниматься ОО-программированием, не изучив модификаторы доступа, интерфейсы и т. Д. То же самое для функционального программирования.

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

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

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

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

    Это может быть проще для понимания, если вы представляете изменяемое состояние как "пробел" где ваша программа выполняется, а затем подумайте о временном измерении. В момент времени 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 с тем же указателем / дескриптором, и он запишет в ту же ячейку памяти & # 8212; но, поскольку концептуально он возвращает новое (поддельное) состояние, состояние (поддельное) все еще остается неизменным (новое было создано). Компилятор будет вызывать функции в том порядке, в котором он должен был бы вызывать их, если бы существовала переменная реального состояния, которую нужно было вычислить, но единственным состоянием, которое там было, является полное (изменяемое) состояние реального оборудования.

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

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

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

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

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

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

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

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

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

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

    Используя много рекурсии.

    Tic Tac Toe в F # (функциональный язык.)

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

    Вот пример:

    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() ) );
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top