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

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

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

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

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

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

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

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

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

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

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

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

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

 using System; namespace ConsoleApplication1 { static class Stack { public static Stack Cons(T hd, Stack tl) { return new Stack(hd, tl); } public static Stack Append(Stack x, Stack y) { return x == null ? y : Cons(x.Head, Append(x.Tail, y)); } public static void Iter(Stack x, Action f) { if (x != null) { f(x.Head); Iter(x.Tail, f); } } } class Stack { public readonly T Head; public readonly Stack Tail; public Stack(T hd, Stack tl) { this.Head = hd; this.Tail = tl; } } class Program { static void Main(string[] args) { Stack x = Stack.Cons(1, Stack.Cons(2, Stack.Cons(3, Stack.Cons(4, null)))); Stack y = Stack.Cons(5, Stack.Cons(6, Stack.Cons(7, Stack.Cons(8, null)))); Stack 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 xy = 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 xy iter (fun a -> printfn "%i" a) z 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 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 (xy) (if (pxy) (f-helper (gxy) y) (hxy))))) (f-helper ey))) 

или этот код Haskellish

 f_fun y = h x_final y where x_initial = e x_final = loop x_initial loop x = if pxy then loop (gxy) else x 

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

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

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

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

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

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

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

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

Рассмотрим функцию с типом s -> (a, s) . Переходя из синтаксиса Haskell, это означает функцию, которая принимает один параметр типа « s » и возвращает пару значений типов « a » и « s ». Если s является типом нашего состояния, эта функция принимает одно состояние и возвращает новое состояние и, возможно, значение (вы всегда можете вернуть «unit» aka () , что эквивалентно « 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 с тем же указателем / дескриптором, и он будет записываться в одно и то же место в памяти – но, поскольку концептуально он возвращает новое (поддельное) состояние, (фальшивое) состояние все еще может быть imutable (новый был ” создано “). Компилятор будет вызывать функции в том порядке, в котором он должен был бы вызывать их, если бы существовала переменная реального состояния, которую нужно было вычислить, но единственным состоянием, которое есть, является полное (изменяемое) состояние реального оборудования.

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

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

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

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

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

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

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

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

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

  • CSSPlay: Лабиринт игры
  • CSSPlay: Maze Game 2
  • CSSPlay: Tic-Tac-Toe
  • Чистый CSS Tic-Tac-Toe
  • CSSPlay: Понг
  • CSSPlay: Ping-Pong
  • CSSPlay: полицейские и разбойники
  • CSSPlay: Whack-a-Rat
  • CSS3 Pong: Insane Что касается CSS

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

  • CSSPlay: Случайные герои
  • Анимированные аналоговые часы SVG
  • Анимированный маятник SVG
  • Анимированные гонщики SVG
  • CSS3: Создание снега

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

  • XSLT Mandlebrot

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

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

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

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

Имейте в виду: функциональные языки завершены. Therefore, any useful task you would perform in an imperitive language can be done in a functional language. At the end of the day though, I think there’s something to be said of a hybrid approach. Languages like F# and Clojure (and I’m sure others) encourage stateless design, but allow for mutability when necessary.

You can’t have a pure functional language that is useful. There will always be a level of mutability that you have to deal with, IO is one example.

Think of functional languages as just another tool that you use. Its good for certain things, but not others. The game example you gave might not be the best way to use a functional language, at least the screen will have a mutable state that you can’t do anything about with FP. The way you think of problem and the type of problems you solve with FP will be different from ones you are used to with imperative programming.

By using lots of recursion.

Tic Tac Toe in F# (A functional language.)

Это очень просто. You can use as many variables as you want in functional programming…but only if they’re local variables (contained inside functions). So just wrap your code in functions, pass values back and forth among those functions (as passed parameters and returned values)…and that’s all there is to it!

Вот пример:

 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() ) ); 
Interesting Posts

«Отсутствует оператор возврата» внутри if / for / while

Инициализация указателя в отдельной функции в C

Воспроизвести фильм с Windows Media на DLNA TV: «не удалось получить информацию о медиа с медиа-сервера»

iPhone: как использовать performSelector: onThread: withObject: waitUntilDone: метод?

Недействительное приложение Win32 на 64-битной машине

Как подождать предупреждения в Selenium webdriver?

Как фильтровать logcat в Android Studio?

Как разбить одну строку на несколько строк, разделенных хотя бы одним пространством в оболочке bash?

Mongodb нахожу созданные результаты по дате сегодня

Изучение других оконных машин 8

Не удается установить драйвер tap с помощью tapinstall.exe для OpenVPN при запуске из файла .bat, но он работает при запуске установщика OpenVPN

Невозможно выполнить команду ping AWS EC2

Как заставить Android WakeLock работать?

Проверьте, является ли массив подмножеством другого

Вставка из нескольких строк в одну строку каждой 6-й ячейки

Давайте будем гением компьютера.