├── 1-introduction.md ├── 2-an-analogy-with-structured-programming.md ├── 3-gluing-functions-together.md ├── 4-gluing-programs-together.md ├── 5-an-example-from-artificial-intelligence.md ├── 6-conclusion.md ├── README.md ├── abstract.md ├── acknowledgments.md └── engRusProgrammersDictionary.md /1-introduction.md: -------------------------------------------------------------------------------- 1 | #Introduction 2 | #Введение 3 | 4 | 5 | This paper is an attempt to demonstrate to the larger community of (nonfunctional) 6 | programmers the significance of functional programming, and also 7 | to help functional programmers exploit its advantages to the full by making it 8 | clear what those advantages are. 9 | 10 | В работе предпринята попытка показать "нефункциональному большинству" программистов 11 | всю важность и значимость функционального программирования. Всем, кто знаком 12 | с функциональным стилем, работа поможет в полной мере насладиться его преимуществами, 13 | разъяснив детали. 14 | 15 | Functional programming is so called because its fundamental operation is 16 | the application of functions to arguments. A main program itself is written as a function 17 | that receives the program’s input as its argument and delivers the program’s output as its result. 18 | Typically the main function is defined in terms of 19 | other functions, which in turn are defined in terms of still more functions, until 20 | at the bottom level the functions are language primitives. All of these functions 21 | are much like ordinary mathematical functions, and in this paper they will be 22 | defined by ordinary equations. We are following Turner’s language Miranda[4]2 23 | here, but the notation should be readable without specific knowledge of this. 24 | 25 | Функциональным программирование называется по одной простой причине - в его основе лежит 26 | аппликация, операция применения функции к ее аргументам. По сути, в функциональном программировании 27 | программа является функцией, которая применяется к входным данным программы, а как результат вычисления 28 | формирует выход программы. Обычно основная функция является суперпозицией более простых функций, 29 | которые, в свою очередь, являются суперпозициями над еще более элементарными функциями, и так 30 | до тех пор, пока мы не опустимся до уровня семантических примитивов. Указанные выше функции 31 | весьма похожи на обычные математические функции, и в нашей работе мы будем задавать их в виде 32 | обычных систем уравнений. В работе мы будем использовать язык Miranda[4], разработанный Тернером, 33 | однако, все приводимые в работе выражения должны быть понятны читателю без каких-либо специальных 34 | познаний в данном языке. 35 | 36 | The special characteristics and advantages of functional programming are 37 | often summed up more or less as follows. Functional programs contain no 38 | assignment statements, so variables, once given a value, never change. More 39 | generally, functional programs contain no side-effects at all. A function call 40 | can have no effect other than to compute its result. This eliminates a major 41 | source of bugs, and also makes the order of execution irrelevant — since no sideeffect 42 | can change an expression’s value, it can be evaluated at any time. This 43 | relieves the programmer of the burden of prescribing the flow of control. Since 44 | expressions can be evaluated at any time, one can freely replace variables by 45 | their values and vice versa — that is, programs are “referentially transparent”. 46 | This freedom helps make functional programs more tractable mathematically 47 | than their conventional counterparts. 48 | 49 | Зачастую к преимуществам функционального программирования, как и его отличительным чертам, 50 | относят следующие его особенности. В программах, написанных в функциональном стиле, 51 | отсутствуют операции присваивания, то есть переменная, однажды получив значение, 52 | более его уже не меняет. Вызов функции никоим образом не влияет на вызывающий контекст, 53 | кроме как возвращает результат своих вычислений. Последнее устраняет основной 54 | источник проблем и ошибок в программах - побочные эффекты вычислений, кроме того, 55 | отсутствие побочных эффектов вычислений позволяет не заботиться о порядке и времени их выполнения, 56 | поскольку делает их независимыми в смысле взаимного влияния на вычисляемые значения. 57 | Все это освобождает программиста от тяжкого бремени управления 58 | [порядком вычислений](https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D0%BA_%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F). 59 | Коль скоро время вычисления выражений не имеет значения, переменные могут быть легко заменены 60 | их значениями и наоборот; программы, обладающие таким свойством, называют 61 | [ссылочно прозрачными](https://en.wikipedia.org/wiki/Referential_transparency). 62 | Перечисленные свойства позволяют облегчить математическую интерпретацию программ, написанных 63 | в функциональном стиле, в сравнении с их традиционными аналогами. 64 | 65 | Such a catalogue of “advantages” is all very well, but one must not be surprised 66 | if outsiders don’t take it too seriously. It says a lot about what functional 67 | programming isn’t (it has no assignment, no side effects, no flow of control) but 68 | not much about what it is. The functional programmer sounds rather like a 69 | mediæval monk, denying himself the pleasures of life in the hope that it will 70 | make him virtuous. To those more interested in material benefits, these 71 | “advantages” are totally unconvincing. 72 | 73 | Все эти "рюшечки" функционального программирования без всякого сомнения хороши, 74 | однако, зачастую малоубедительны. Они указывают на то, чего в функциональном 75 | программировании нет (нет присваиваний, нет побочных эффектов, нет порядка вычислений), 76 | однако, не отвечают на вопрос, чем же этот мир наполнен. В этом смысле программист, 77 | практикующий функциональный подход, похож на средневекового монаха, лишившего себя 78 | всех прелестей жизни в надежде, что это добавит ему добродетели. "Мирянам" же с их 79 | плотскими страстями все это кажется пустой тратой времени. 80 | 81 | Functional programmers argue that there are great material benefits — that 82 | a functional programmer is an order of magnitude more productive than his 83 | or her conventional counterpart, because functional programs are an order of 84 | magnitude shorter. Yet why should this be? The only faintly plausible reason 85 | one can suggest on the basis of these “advantages” is that conventional programs 86 | consist of 90% assignment statements, and in functional programs these can be 87 | omitted! This is plainly ridiculous. If omitting assignment statements brought 88 | such enormous benefits then Fortran programmers would have been doing it 89 | for twenty years. It is a logical impossibility to make a language more powerful 90 | by omitting features, no matter how bad they may be. 91 | 92 | Программисты, приверженцы функционального подхода, возражают на это, утверждая, 93 | что они на порядок продуктивнее своих "обыкновенных" визави, поскольку 94 | программы, написанные в функциональном стиле, на порядок короче. Конечно, 95 | а как же еще? Однако, положив руку на сердце, признаемся, что подобные рассуждения 96 | справедливы лишь в случае, если бы "обычная" программа состояла на 90% из 97 | операций присваивания, а применение функциональной парадигмы просто бы их все устранило. 98 | Выглядит глупо. Если бы устранение операции присваивания давало бы такие громадные 99 | преимущества, то пальму первенства в продуктивности последние 20 лет должны были бы 100 | удерживать программисты Fortran'а. К сожалению, устранение из языка какой-либо его 101 | особенности, какой бы плохой она не была, в подавляющем большинстве случаев не делает 102 | язык лучше. 103 | 104 | Even a functional programmer should be dissatisfied with these so-called 105 | advantages, because they give no help in exploiting the power of functional languages. 106 | One cannot write a program that is particularly lacking in assignment 107 | statements, or particularly referentially transparent. There is no yardstick of 108 | program quality here, and therefore no ideal to aim at. 109 | 110 | Более того, даже поклонники функционального подхода не жалуют эти, так называемые 111 | "преимущества", поскольку последние ничуть не помогают раскрыть всю мощь 112 | и прелесть функциональных языков. Кто-то не может обойтись без операций 113 | присваивания при написании программы, кому-то тяжело добиться идеальной 114 | ссылочной прозрачности. В такой системе координат нет мерила качества программы, 115 | а потому нет и идеала, к которому следовало бы стремиться. 116 | 117 | Clearly this characterization of functional programming is inadequate. We 118 | must find something to put in its place — something that not only explains the 119 | power of functional programming but also gives a clear indication of what the 120 | functional programmer should strive towards. 121 | 122 | Совершенно понятно, что подобный список "преимуществ" языка не может быть адекватным. 123 | Мы должны найти нечто более подходящее, нечто, что позволит не только наглядно раскрыть 124 | мощь функционального подхода, но также даст и четкие критерии качества создаваемых 125 | программ, которых должен придерживаться программист, использующий функциональный 126 | стиль программирования. 127 | -------------------------------------------------------------------------------- /2-an-analogy-with-structured-programming.md: -------------------------------------------------------------------------------- 1 | # 2 An analogy with Structured Programming 2 | 3 | It’s helpful to draw an analogy between functional and structured programming. 4 | In the past, the characteristics and advantages of structured programming have 5 | been summed up more or less as follows. Structured programs contain no goto 6 | statements. Blocks in a structured program do not have multiple entries or exits. 7 | Structured programs are more tractable mathematically than their unstructured 8 | counterparts. These “advantages” of structured programming are very similar in 9 | spirit to the “advantages” of functional programming we discussed earlier. They 10 | are essentially negative statements, and have led to much fruitless argument 11 | about “essential gotos” and so on. 12 | With the benefit of hindsight, it’s clear that these properties of structured 13 | programs, although helpful, do not go to the heart of the matter. The most important difference between structured and unstructured programs is that structured programs are designed in a modular way. Modular design brings with 14 | it great productivity improvements. First of all, small modules can be coded 15 | quickly and easily. Second, general-purpose modules can be reused, leading to 16 | faster development of subsequent programs. Third, the modules of a program 17 | can be tested independently, helping to reduce the time spent debugging. 18 | 19 | Полезно провести аналогию между функциональным и структурным программированием. В прошлом характеристики и преимущества структурного программирования были более-менее определены в следующих чертах: такие программы не содержат операторов goto, программные блоки в них не имеют множественных входов и выходов, и структурные программы более математически согласованы в отличие от их неструктурированных аналогов. Эти «преимущества» структурного программирования очень похожи по духу на «преимущества» функционального программирования, о котором мы упоминали ранее. По сути они являются негативными утверждениями и привели к большому количеству бесплодных агрументов типа « необходимые операторы goto» и т.д. Оглядываясь назад, становится ясно что эти свойства структурированных программ хоть и полезны, но не раскрывают сути вопроса. Самым важным различием структурированных программ от неструктурированных является то, что структурированные программы разработаны по модульному принципу. Модульная структура программ сама по себе влияет на повышение производительности. Прежде всего, небольшие модули могут быть написаны быстро и легко. Во-вторых, универсальные модули могут быть использованы повторно, что приводит к более быстрому развитию последующих программ. В-третьих, модули программы могут быть проверены независимо друг от друга, что помогает сократить время, затрачиваемое на отладку. 20 | 21 | The absence of gotos, and so on, has very little to do with this. It helps with 22 | “programming in the small”, whereas modular design helps with “programming 23 | in the large”. Thus one can enjoy the benefits of structured programming in 24 | Fortran or assembly language, even if it is a little more work. 25 | It is now generally accepted that modular design is the key to successful 26 | programming, and recent languages such as Modula-II [6] and Ada [5] include 27 | features specifically designed to help improve modularity. 28 | 29 | Отсутствие goto и т.п. имеет мало общего с этим. Оно помогает при «программировании в малом», в то время как модульная структура помогает при «программировании в целом». Таким образом, можно пользоваться преимуществами структурного программирования на языке Фортран или ассемблере, даже если это приводит к небольшому увеличению работы. В настоящее время принято считать, что модульная конструкция является ключом к успешному программированию и такие языки как Modula-II [6] и Ада [5] включают в себя функции, специально предназначенные для улучшения модульности. 30 | 31 | However, there is a very important point that is often missed. When writing a modular program to 32 | solve a problem, one first divides the problem into subproblems, then solves the 33 | subproblems, and finally combines the solutions. The ways in which one can 34 | divide up the original problem depend directly on the ways in which one can glue 35 | solutions together. Therefore, to increase one’s ability to modularize a problem 36 | conceptually, one must provide new kinds of glue in the programming language. 37 | Complicated scope rules and provision for separate compilation help only with 38 | clerical details — they can never make a great contribution to modularization. 39 | We shall argue in the remainder of this paper that functional languages provide two new, very important kinds of glue. We shall give some examples of programs that can be modularized in new ways and can thereby be simplified. 40 | This is the key to functional programming’s power — it allows improved modularization. It is also the goal for which functional programmers must strive — smaller and simpler and more general modules, glued together with the new 41 | glues we shall describe. 42 | 43 | Однако, есть очень важный момент, который часто упускается. Чтобы решить проблему при написании модульной программы, необходимо сначала разделить проблему на подзадачи, затем решить подзадачи и, в конечном итоге, скомбинировать решения подзадач. Способы, которыми можно разделить исходную проблему на подзадачи, непосредственно зависят от способов, которыми можно «склеить» решения подзадач вместе. Поэтому, чтобы концептуально увеличить свою способность модуляризировать проблему, нужно обеспечить новые виды «клея» на языке программирования. Запутанные правила и обеспечение отдельной компиляции помогают только в несущественных деталях, которые никогда не смогут внести большой вклад в модульность программы. В остальной части этой работы мы утверждаем, что функциональные языки программирования предоставляют два новых, очень важных видов «клея». Мы приведем некоторые примеры программ, которые могут быть модуляризированы новыми способами и, соответственно, упрощены. Это ключ к мощи функционального программирования, который позволяет улучшить модульность программ. Это также цель, к которой функциональные программисты должны стремиться - меньше и проще структура и более универсальные модули, склеенные новыми клеями, которые мы опишем. 44 | 45 | -------------------------------------------------------------------------------- /3-gluing-functions-together.md: -------------------------------------------------------------------------------- 1 | # 3 Композиция функций 2 | 3 | The first of the two new kinds of glue enables simple functions to be glued 4 | together to make more complex ones. It can be illustrated with a simple listprocessing problem — adding the elements of a list. We can define lists by 5 | 6 | Первый из двух новых видов соединений функций, позволяет функциям образовывать более сложные. Это можно увидеть на примере работы с простым списком -- добавления элементов в список. Мы можем определять списки как 7 | 8 | ```image 9 | ``` 10 | 11 | which means that a list of ∗s (whatever ∗ is) is either Nil , representing a list with no elements, or a Cons of a ∗ and another list of ∗s. A Cons represents a list whose first element is the ∗ and whose second and subsequent elements are the elements of the other list of ∗s. Here ∗ may stand for any type — for example, if ∗ is “integer” then the definition says that a list of integers is either empty or a Cons of an integer and another list of integers. Following normal practice, we will write down lists simply by enclosing their elements in square brackets, rather than by writing Conses and Nil s explicitly. This is simply a shorthand for notational convenience. For example, 12 | 13 | что означает, что список ∗s (независимо от ∗) - это либо `Nil`, представляющий собой список без элементов, или `Cons` для `∗` и другого списка `∗s`. `Cons` является списком, первый элемент которого `∗`, а второй и последующие элементы являются элементами списка `∗s`. Здесь `∗` может обозначать любой тип данных - например, если `∗` является "целым числом", то определение говорит, что список целых чисел либо пуст, либо является `Cons` (соединением) целого числа и другого списка целых чисел. Следуя принятым обозначениям, мы будем записывать списки просто заключив их элементы в квадратные скобки, не используя `Cons` и `Nil` в явном виде. Это просто сокращение для удобства записи. Например, 14 | 15 | ``` 16 | [ ] то же самое, что и Nil 17 | [1] то же самое, что и Cons 1 Nil 18 | [1, 2, 3] то же самое, что и Cons 1 (Cons 2 (Cons 3 Nil)) 19 | ``` 20 | 21 | The elements of a list can be added by a recursive function sum. The function 22 | sum must be defined for two kinds of argument: an empty list (Nil ), and a 23 | Cons. Since the sum of no numbers is zero, we define 24 | 25 | Элементы списка могут быть сложены с помощью рекурсивной функции `sum`. Функция должна быть определена для двух аргументов: для пустого списка (`Nil`) и для `Cons`. Так как сумма нуля чисел равна нулю мы определяем 26 | 27 | ``` 28 | sum Nil = 0 29 | ``` 30 | 31 | and since the sum of a Cons can be calculated by adding the first element of 32 | the list to the sum of the others, we can define 33 | 34 | и сумма `Cons` может быть вычислена путем добавления первого элемента 35 | списка к сумме других, мы можем определить 36 | 37 | ``` 38 | sum (Cons n list) = num + sum list 39 | ``` 40 | 41 | Examining this definition, we see that only the boxed parts below are specific 42 | to computing a sum. 43 | 44 | Посмотрев на это определение, можно видеть, что только в первой части приведены конкретные действия для вычисления суммы. 45 | 46 | ``` 47 | sum Nil = 0 48 | sum (Cons n list) = n + sum list 49 | ``` 50 | 51 | This means that the computation of a sum can be modularized by gluing 52 | together a general recursive pattern and the boxed parts. This recursive pattern is conventionally called foldr and so sum can be expressed as 53 | 54 | This means that the computation of a sum can be modularized by gluing 55 | together a general recursive pattern and the boxed parts. Этот рекурсивный шаблон условно называется `foldr` и поэтому сумма может быть выражена как 56 | 57 | ``` 58 | sum = foldr (+) 0 59 | ``` 60 | 61 | The definition of foldr can be derived just by parameterizing the definition of 62 | sum, giving 63 | 64 | Определение `foldr` может быть получено путем параметризации определения функции `sum` следующим образом 65 | 66 | ``` 67 | (foldr f x) Nil = x 68 | (foldr f x) (Cons a l ) = f a ((foldr f x) l ) 69 | ``` 70 | 71 | Here we have written brackets around (foldr f x) to make it clear that it replaces sum. Conventionally the brackets are omitted, and so ((foldr f x) l) is written as (foldr f x l). A function of three arguments such as foldr, applied to only two, is taken to be a function of the one remaining argument, and in general, a function of n arguments applied to only m of them (m < n) is taken to be a function of the n − m remaining ones. We will follow this convention in future. 72 | 73 | Здесь мы пришем скобки вокруг `(foldr f x)` чтобы более ясно показать, что это заменяет функцию `sum`. Скобки могут быть опущены, и `((foldr f x) l)` может быть записано как `(foldr f x l)`. Функции трех аргументов, такие как `foldr`, применяются к двум рагументам, а затем получается функция от одного аргумента, и в общем случае, функция `n` аргументов, которая применяется к `m` аргументам, где m < n дает функцию от `n − m` оставшихся аргументов. 74 | 75 | Having modularized sum in this way, we can reap benefits by reusing the 76 | parts. The most interesting part is foldr, which can be used to write down a 77 | function for multiplying together the elements of a list with no further programming: 78 | 79 | Записав функцию `sum` в таком виде, мы можем получить выгодную возможность переиспользования частей определения. Самое интересное, что `foldr` может использоваться, для записи функции умножения элементов списка без дальнейшей доработки: 80 | 81 | ``` 82 | product = foldr (∗) 1 83 | ``` 84 | 85 | It can also be used to test whether any of a list of booleans is true 86 | 87 | Можно так же использовать `foldr` проверки того, является ли хотя бы одно значение списка булевых значений истинным 88 | 89 | ``` 90 | anytrue = foldr (∨) F alse 91 | ``` 92 | 93 | or whether they are all true 94 | 95 | или являются ли все значения истинными 96 | 97 | ``` 98 | alltrue = foldr (∧) True 99 | ``` 100 | 101 | One way to understand (foldr f a) is as a function that replaces all occurrences of Cons in a list by f , and all occurrences of Nil by a. Taking the list [1, 2, 3] as an example, since this means 102 | 103 | Один из способов понять `(foldr f a)` как функцию состоит в том, чтобы заменить все вхождения `Cons` в списке на `f` , а все вхождения `Nil` на `a`. Например, [1, 2, 3] будет обозначать то же, что и 104 | 105 | ``` 106 | Cons 1 (Cons 2 (Cons 3 Nil )) 107 | ``` 108 | 109 | then (foldr (+) 0) converts it into 110 | 111 | тогда `(foldr (+) 0)` преобразуется в 112 | 113 | ``` 114 | (+) 1 ((+) 2 ((+) 3 0)) = 6 115 | ``` 116 | 117 | и `(foldr (∗) 1)` преобразуется в 118 | 119 | ``` 120 | (∗) 1 ((∗) 2 ((∗) 3 1)) = 6 121 | ``` 122 | 123 | Now it’s obvious that (foldr Cons Nil ) just copies a list. Since one list can be appended to another by Cons ing its elements onto the front, we find 124 | 125 | Теперь очевидно, что `(foldr Cons Nil)` просто копирует список. Так как список ещё может быть расширен путем добавления новых элементов в голову с помощью `Cons`, то мы определяем 126 | 127 | ``` 128 | append a b = foldr Cons b a 129 | ``` 130 | 131 | As an example, 132 | 133 | Например, 134 | 135 | ``` 136 | append [1, 2] [3, 4] = foldr Cons [3, 4] [1, 2] 137 | = foldr Cons [3, 4] (Cons 1 (Cons 2 Nil )) 138 | = Cons 1 (Cons 2 [3, 4])) 139 | (replacing Cons by Cons and Nil by [3, 4]) 140 | = [1, 2, 3, 4] 141 | ``` 142 | 143 | We can count the number of elements in a list using the function length, defined by 144 | 145 | Мы можем подсчитать количество элементов в списке с помощью функции `length`, определяемой как 146 | 147 | `` 148 | length = foldr count 0 149 | count a n = n + 1 150 | `` 151 | 152 | because count increments 0 as many times as there are Cons es. A function that 153 | doubles all the elements of a list could be written as 154 | 155 | потому что `count` добавляет единицу к нулю столько раз, сколько элементов присутствует в списке. Функция, которая дублирует элементы списка может быть записана как 156 | 157 | ``` 158 | doubleall = foldr doubleandcons Nil 159 | ``` 160 | 161 | where 162 | 163 | где 164 | 165 | ``` 166 | doubleandcons n list = Cons (2 ∗ n) list 167 | ``` 168 | 169 | The function doubleandcons can be modularized even further, first into 170 | 171 | Функция `doubleandcons` может быть обобщена ещё больше 172 | 173 | ``` 174 | doubleandcons = f andcons double 175 | ``` 176 | 177 | where 178 | 179 | где 180 | 181 | ``` 182 | double n = 2 ∗ n 183 | f andcons f el list = Cons (f el ) list 184 | ``` 185 | 186 | and then by 187 | 188 | и тогда поскольку 189 | 190 | ``` 191 | f andcons f = Cons . f 192 | ``` 193 | 194 | where “.” (function composition, a standard operator) is defined by 195 | 196 | где “.” (функция композиции, стандартный оператор) определенная как 197 | 198 | ``` 199 | (f . g) h = f (g h) 200 | ``` 201 | 202 | We can see that the new definition of f andcons is correct by applying it to some arguments: 203 | 204 | Мы можем видеть, что новое определение `f andcons` будет правильным будучи примененным к некоторым аргументам: 205 | 206 | ``` 207 | f andcons f el 208 | 209 | = (Cons . f ) el 210 | = Cons (f el ) 211 | ``` 212 | 213 | so 214 | 215 | так 216 | 217 | ``` 218 | f andcons f el list = Cons (f el ) list 219 | ``` 220 | 221 | The final version is 222 | 223 | Финальная версия 224 | 225 | ``` 226 | doubleall = foldr (Cons . double) Nil 227 | ``` 228 | 229 | With one further modularization we arrive at 230 | 231 | ``` 232 | doubleall = map double 233 | map f = foldr (Cons . f ) Nil 234 | ``` 235 | 236 | where map — another generally useful function — applies any function f to all 237 | the elements of a list. 238 | 239 | We can even write a function to add all the elements of a matrix, represented 240 | as a list of lists. It is 241 | 242 | ``` 243 | summatrix = sum . map sum 244 | ``` 245 | 246 | The function map sum uses sum to add up all the rows, and then the leftmost 247 | sum adds up the row totals to get the sum of the whole matrix. 248 | These examples should be enough to convince the reader that a little modularization can go a long way. By modularizing a simple function (sum) as a 249 | combination of a “higher-order function” and some simple arguments, we have 250 | arrived at a part (f oldr) that can be used to write many other functions on lists 251 | with no more programming effort. 252 | We do not need to stop with functions on lists. As another example, consider 253 | the datatype of ordered labeled trees, defined by 254 | 255 | ``` 256 | treeof ∗ ::= Node ∗ (listof (treeof ∗)) 257 | ``` 258 | 259 | This definition says that a tree of ∗s is a node, with a label which is a ∗, and a 260 | list of subtrees which are also trees of ∗s. For example, the tree 261 | ```image 262 | ``` 263 | would be represented by 264 | 265 | ``` 266 | Node 1 267 | (Cons (Node 2 Nil ) 268 | (Cons (Node 3 269 | (Cons (Node 4 Nil ) Nil )) 270 | Nil )) 271 | ``` 272 | 273 | Instead of considering an example and abstracting a higher-order function from 274 | it, we will go straight to a function foldtree analogous to foldr. Recall that foldr 275 | took two arguments: something to replace Cons with and something to replace 276 | Nil with. Since trees are built using Node, Cons, and Nil , foldtree must take 277 | three arguments — something to replace each of these with. Therefore we define 278 | 279 | ``` 280 | foldtree f g a (Node label subtrees) = 281 | f label (foldtree f g a subtrees) 282 | foldtree f g a (Cons subtree rest) = 283 | g (foldtree f g a subtree) (foldtree f g a rest) 284 | foldtree f g a Nil = a 285 | ``` 286 | 287 | Many interesting functions can be defined by gluing foldtree and other functions 288 | together. For example, all the labels in a tree of numbers can be added together 289 | using 290 | 291 | ``` 292 | sumtree = foldtree (+) (+) 0 293 | ``` 294 | 295 | Taking the tree we wrote down earlier as an example, sumtree gives 296 | 297 | ``` 298 | (+) 1 299 | ((+) ((+) 2 0) 300 | ((+) ((+) 3 301 | ((+) ((+) 4 0) 0)) 302 | 0)) 303 | = 10 304 | ``` 305 | 306 | A list of all the labels in a tree can be computed using 307 | 308 | labels = foldtree Cons append Nil 309 | 310 | The same example gives 311 | 312 | ``` 313 | Cons 1 314 | (append (Cons 2 Nil ) 315 | (append (Cons 3 316 | (append (Cons 4 Nil ) Nil )) 317 | Nil )) 318 | = [1, 2, 3, 4] 319 | ``` 320 | 321 | Finally, one can define a function analogous to map which applies a function f 322 | to all the labels in a tree: 323 | 324 | ``` 325 | maptree f = foldtree (Node . f ) Cons Nil 326 | ``` 327 | 328 | All this can be achieved because functional languages allow functions that are 329 | indivisible in conventional programming languages to be expressed as a combinations of parts — a general higher-order function and some particular specializing 330 | functions. Once defined, such higher-order functions allow many operations to 331 | be programmed very easily. Whenever a new datatype is defined, higher-order 332 | functions should be written for processing it. This makes manipulating the 333 | datatype easy, and it also localizes knowledge about the details of its representation. The best analogy with conventional programming is with extensible 334 | languages — in effect, the programming language can be extended with new 335 | control structures whenever desired. 336 | -------------------------------------------------------------------------------- /4-gluing-programs-together.md: -------------------------------------------------------------------------------- 1 | # Gluing programs together 2 | # Соединение программ вместе 3 | 4 | The other new kind of glue that functional languages provide enables whole 5 | programs to be glued together. Recall that a complete functional program is 6 | just a function from its input to its output. If f and g are such programs, then 7 | (g . f ) is a program that, when applied to its input, computes 8 | 9 | 10 | Функциональные языки позволяют и другой вид соединения - целые программы могут быть соединены вместе. 11 | Напомним, что полностью функциональная программа это просто функция от ее входных параметров с результатом 12 | вычисления на выходе. Если f и g такие программы, то (g . f) это программа, которой на вход подается 13 | результат вычисления функции и которая вычисляет 14 | 15 | 16 | g (f input) 17 | 18 | The program f computes its output, which is used as the input to program g. 19 | This might be implemented conventionally by storing the output from f in a 20 | temporary file. The problem with this is that the temporary file might occupy 21 | so much memory that it is impractical to glue the programs together in this way. 22 | Functional languages provide a solution to this problem. The two programs f 23 | and g are run together in strict synchronization. Program f is started only 24 | when g tries to read some input, and runs only for long enough to deliver the 25 | output g is trying to read. Then f is suspended and g is run until it tries to read 26 | another input. As an added bonus, if g terminates without reading all of f ’s 27 | output, then f is aborted. Program f can even be a nonterminating program, 28 | producing an infinite amount of output, since it will be terminated forcibly as 29 | soon as g is finished. This allows termination conditions to be separated from 30 | loop bodies — a powerful modularization. 31 | 32 | 33 | Программа f вычисляет результат на выходе, который применяется как входные данные в программе g. 34 | Это можно реализовать обычным способом, сохранив результат вычисления программы f во временном файле. 35 | Но в этом случае временный файл может занять слишком много памяти и соединение программ вместе таким 36 | способом становится нецелесообразным. Функциональные языки предоставляют решение этой проблемы. 37 | Обе программы f и g выполняются вместе в строгой последовательности: когда программа g пытается прочитать 38 | некоторые входные данные запускается программа f и выполняется до появления результата вычисления, 39 | который программа g считывает. 40 | Затем f приостанавливается и g выполняется пока ей не понадобятся другие входные данные. 41 | В качестве дополнительного бонуса, если g завершается без считывания всех выходных данных f то выполнение f будет прервано. 42 | Программа f может быть даже бесконечной и вычисляющей безграничное количество выходных данных, 43 | но как только программа g будет завершена также принудительно прекращается выполнение программы f. 44 | Это позволяет отделить условия завершения от тел циклов - мощная модуляризация. 45 | 46 | 47 | Since this method of evaluation runs f as little as possible, it is called “lazy 48 | evaluation”. It makes it practical to modularize a program as a generator that 49 | constructs a large number of possible answers, and a selector that chooses the 50 | appropriate one. While some other systems allow programs to be run together 51 | in this manner, only functional languages (and not even all of them) use lazy 52 | evaluation uniformly for every function call, allowing any part of a program to 53 | be modularized in this way. Lazy evaluation is perhaps the most powerful tool 54 | for modularization in the functional programmer’s repertoire. 55 | 56 | 57 | Поскольку этот метод вычисления запускает f как можно реже, то он получил название "ленивые (отложенные) 58 | вычисления". Целесообразнее для увеличения модульности программы проектировать ее в качестве генератора, 59 | который строит большое количество возможных ответов, и селектора, который выбирает соответствующий ответ. 60 | В то время как некоторые другие системы позволяют программам работать вместе в этом ключе, 61 | только функциональные языки (и даже не все из них) используют ленивые вычисления равномерно для каждого 62 | вызова функции, позволяя таким образом любой части программы быть модульной. Ленивые вычисления, возможно, 63 | самый мощный инструмент для увеличения модульности в арсенале функциональных программистов. 64 | 65 | 66 | We have described lazy evaluation in the context of functional languages, 67 | but surely so useful a feature should be added to nonfunctional languages — 68 | or should it? с Unfortunately, they cannot: Adding lazy evaluation to an imperative notation is not 69 | actually impossible, but the combination would make the programmer’s life harder, rather than 70 | easier. Because lazy evaluation’s power depends on the programmer giving up 71 | any direct control over the order in which the parts of a program are executed, 72 | it would make programming with side effects rather difficult, because predicting 73 | in what order —or even whether— they might take place would require knowing 74 | a lot about the context in which they are embedded. Such global interdependence would defeat 75 | the very modularity that — in functional languages — lazy evaluation is designed to enhance. 76 | 77 | 78 | Мы описали ленивые вычисления в контексте функциональных языков, но несомненно, такую полезную особенность 79 | следует добавить в нефункциональные языки - или не так? Могут ли ленивые вычисления и побочные эффекты 80 | сосуществовать? К сожалению, не могут: добавление ленивых вычислений в императивной нотации фактически 81 | возможно, но такое сочетание сделало бы жизнь программиста труднее, нежели легче. Поскольку мощь ленивых вычислений 82 | зависит от того, сможет ли программист отказаться от прямого контроля над порядком исполнения программы, 83 | это сделало бы программирование с побочными эффектами весьма сложным, потому что прогнозирование 84 | в каком порядке или даже наличия побочных эффектов потребовало бы знать весь контекст ситуации. 85 | Такая глобальная взаимозависимость могла бы разрушить ту самую модульность, которую - в функциональных языках - ленивые вычисления предназначены повысить. 86 | 87 | 88 | ## 4.1 Newton-Raphson Square Roots 89 | 90 | 91 | ## 4.1 Квадратный корень Ньютона-Рафсона. 92 | 93 | 94 | 95 | We will illustrate the power of lazy evaluation by programming some numerical 96 | algorithms. First of all, consider the Newton-Raphson algorithm for finding 97 | square roots. This algorithm computes the square root of a number n by starting 98 | from an initial approximation a0 and computing better and better ones using 99 | the rule 100 | 101 | 102 | Мы проиллюстрируем мощь ленивых вычислений, программируя некоторые вычислительные 103 | алгоритмы. Прежде всего, рассмотрим алгоритм Ньютона-Рафсона для нахождения 104 | квадратного корня. Этот алгоритм вычисляет квадратный корень из числа п, начиная 105 | от начального приближения а0 и вычисляя более точное число, используя 106 | правило 107 | 108 | 109 | ai+1 = (ai + n/ai )/2 110 | 111 | If the approximations converge to some limit a, then 112 | 113 | 114 | Если приближения сходятся к некоторому пределу а, то 115 | 116 | 117 | a = (a + n/a)/2 118 | 119 | so 120 | 121 | 122 | таким образом 123 | 124 | 125 | 2a = a + n/a 126 | a = n/a 127 | a∗a = n 128 | a = √n 129 | 130 | In fact the approximations converge rapidly to a limit. Square root programs 131 | take a tolerance (eps) and stop when two successive approximations differ by 132 | less than eps. 133 | The algorithm is usually programmed more or less as follows: 134 | 135 | 136 | На самом деле приближения быстро сходятся к некоторому пределу. Программа вычисляет 137 | квадратный корень с заданной точностью (eps - машинный эпсилон) и останавливается, 138 | когда два последовательных приближения отличаются 139 | друг от друга на величину меньшую величине eps. 140 | Этот алгоритм обычно реализуется следующим образом: 141 | 142 | 143 | C N IS CALLED ZN HERE SO THAT IT HAS THE RIGHT TYPE 144 | X = A0 145 | Y = A0 + 2. * EPS 146 | C Y’S VALUE DOES NOT MATTER SO LONG AS ABS(X-Y).GT.EPS 147 | 100 IF ABS(X-Y).LE.EPS GOTO 200 148 | Y = X 149 | X = (X + ZN/X) / 2. 150 | GOTO 100 151 | 200 CONTINUE 152 | C THE SQUARE ROOT OF ZN IS NOW IN X. 153 | 154 | This program is indivisible in conventional languages. We will express it in a 155 | more modular form using lazy evaluation and then show some other uses to 156 | which the parts may be put. 157 | 158 | 159 | В традиционных языках эта программа не разделяется. Мы выразим ее в более модульной форме, 160 | используя ленивые вычисления и затем покажем другие виды их применения. 161 | 162 | 163 | Since the Newton-Raphson algorithm computes a sequence of approximations it is natural to represent this explicitly in the program by a list of approximations. Each approximation is derived from the previous one by the function 164 | 165 | Так как алгоритм Ньютона-Рафсона вычисляет последовательность приближений, 166 | естественно представить это в явном виде в программе списком приближений. 167 | Каждое приближение получено из предыдущего с помощью функции 168 | 169 | 170 | next n x = (x + n/x)/2 171 | 172 | so (next n) is the function mapping one approximation onto the next. Calling 173 | this function f , the sequence of approximations is 174 | 175 | Таким образом (next n) является функцией отображения одного приближения на следующее. 176 | Если назовем эту функцию f получим последовательность приближений 177 | 178 | [a0, f a0, f (f a0), f (f (f a0)), . . . ] 179 | 180 | We can define a function to compute this: 181 | 182 | Мы можем определить функцию для вычисления следующего: 183 | 184 | repeat f a = Cons a (repeat f (f a)) 185 | 186 | so that the list of approximations can be computed by 187 | 188 | соответственно список приближений можно вычислить с помощью 189 | 190 | repeat (next n) a0 191 | 192 | The function repeat is an example of a function with an “infinite” output — but 193 | it doesn’t matter, because no more approximations will actually be computed 194 | than the rest of the program requires. The infinity is only potential: All it means 195 | is that any number of approximations can be computed if required; repeat itself 196 | places no limit. 197 | 198 | 199 | Функция repeat является примером функции с "бесконечными" выходными данными - но 200 | это не имеет значения, потому что несмотря на требования оставшейся части программы 201 | приближения больше вычисляться не будут. Бесконечность является только потенциалом: 202 | это означает, что при необходимости любое число приближений можно вычислить; 203 | неоднократное повторение само по себе не накладывает никаких ограничений. 204 | 205 | 206 | The remainder of a square root finder is a function within, which takes a 207 | tolerance and a list of approximations and looks down the list for two successive 208 | approximations that differ by no more than the given tolerance. It can be 209 | defined by 210 | 211 | Нахождение остатка квадратного корня является функцией within, которая принимает 212 | допуск и список приближений и ищет в этом списке два последовательных 213 | риближения, отличающиеся не более чем на данный допуск. Это может быть определено как 214 | 215 | 216 | within eps (Cons a (Cons b rest)) 217 | = b, if abs (a − b) ≤ eps 218 | = within eps (Cons b rest), otherwise 219 | 220 | Putting the parts together, we have 221 | 222 | Соединив эти элементы вместе получим 223 | 224 | 225 | sqrt a0 eps n = within eps (repeat (next n) a0) 226 | 227 | Now that we have the parts of a square root finder, we can try combining 228 | them in different ways. One modification we might wish to make is to wait 229 | for the ratio between successive approximations to approach 1, rather than for 230 | the difference to approach 0. This is more appropriate for very small numbers 231 | (when the difference between successive approximations is small to start with) 232 | and for very large ones (when rounding error could be much larger than the 233 | tolerance). It is only necessary to define a replacement for within: 234 | 235 | Теперь, когда мы имеем элементы нахождения квадратного корня, мы можем попытаться скомбинировать 236 | их по-разному. Одной из разновидностей, которую мы можем реализовать, это дождаться стремящегося к 1 соотношения между последовательными приближениями (в отличие от нахождения стремящейся 237 | к 0 разницы). Это больше подходит для очень маленьких чисел (когда разность между 238 | последовательными приближениями с самого начала мала) и для очень крупных (когда 239 | погрешность округления может быть гораздо больше, чем допуск). Необходимо лишь определить 240 | замену в пределах: 241 | 242 | 243 | relative eps (Cons a (Cons b rest)) 244 | = b, if abs (a/b − 1) ≤ eps 245 | = relative eps (Cons b rest), otherwise 246 | 247 | Now a new version of sqrt can be defined by 248 | 249 | Теперь новая версия sqrt может быть определена 250 | 251 | 252 | relativesqrt a0 eps n = relative eps (repeat (next n) a0) 253 | 254 | It is not necessary to rewrite the part that generates approximations. 255 | 256 | Нет необходимости переписывать часть, которая генерирует приближения. 257 | 258 | 259 | ## 4.2 Numerical Differentiation 260 | 261 | ## 4.2 Численное дифференцирование 262 | 263 | We have reused the sequence of approximations to a square root. Of course, 264 | it is also possible to reuse within and relative with any numerical algorithm 265 | that generates a sequence of approximations. We will do so in a numerical 266 | differentiation algorithm. 267 | 268 | Мы неоднократно использовали последовательность приближений для нахождения квадратного корня. 269 | Конечно, таким же образом можно поступить с любым вычислительным алгоритмом, 270 | который генерирует последовательность приближений. Мы применим этот подход и для 271 | алгоритма численного дифференцирования. 272 | 273 | 274 | The result of differentiating a function at a point is the slope of the function’s 275 | graph at that point. It can be estimated quite easily by evaluating the function 276 | at the given point and at another point nearby and computing the slope of a 277 | straight line between the two points. This assumes that if the two points are 278 | close enough together, then the graph of the function will not curve much in 279 | between. This gives the definition 280 | 281 | Результат дифференцирования функции в некоторой точке является наклон графика функции 282 | в этой точке. Его можно довольно легко предположить путем выполнения функции в данной точке и 283 | в другой близлежащей точке и вычисления наклона прямой линии между двумя этими точками. 284 | При этом предполагается, что если две точки находятся достаточно близко друг к другу, 285 | то график функции между ними будет практически прямой. 286 | Это дает определение 287 | 288 | 289 | easydiff f x h = (f (x + h) − f x)/h 290 | 291 | In order to get a good approximation the value of h should be very small. 292 | Unfortunately, if h is too small then the two values f (x + h) and f (x) are very 293 | close together, and so the rounding error in the subtraction may swamp the 294 | result. How can the right value of h be chosen? One solution to this dilemma 295 | is to compute a sequence of approximations with smaller and smaller values of 296 | h, starting with a reasonably large one. Such a sequence should converge to the 297 | value of the derivative, but will become hopelessly inaccurate eventually due to 298 | rounding error. If (within eps) is used to select the first approximation that 299 | is accurate enough, then the risk of rounding error affecting the result can be 300 | much reduced. We need a function to compute the sequence: 301 | 302 | Для того, чтобы получить хорошее приближение значение h должно быть очень мало. 303 | К сожалению, если h слишком мало, то два значения f (x + h) и f (x) почти равны, 304 | и таким образом погрешность округления при вычитании может привести к неправильному 305 | результату. Как можно выбрать правильное значение h? Одним из путей решения этой дилеммы 306 | является вычисление последовательности приближений с меньшими и меньшими значениями h, 307 | начиная с достаточно большого h. Такая последовательность должна сходиться к 308 | значению производной, но станет безнадежно неточной в конечном счете из-за 309 | погрешности округления. Если (в пределах eps) было выбрано достаточно точное первое 310 | приближение, то риск влияния на результат погрешности округления может быть 311 | значительно снижен. Нам нужна функция для вычисления следующей последовательности: 312 | 313 | 314 | differentiate h0 f x = map (easydiff f x) (repeat halve h0) 315 | halve x = x/2 316 | 317 | Here h0 is the initial value of h, and successive values are obtained by repeated 318 | halving. Given this function, the derivative at any point can be computed by 319 | 320 | Здесь h0 это начальное значение h, а последовательные значения получены повторным 321 | уменьшение в два раза. С учетом этой функции, производная в любой точке может быть 322 | вычислена с помощью 323 | 324 | 325 | within eps (differentiate h0 f x) 326 | 327 | Even this solution is not very satisfactory because the sequence of approximations converges fairly slowly. A little simple mathematics can help here. The 328 | elements of the sequence can be expressed as 329 | 330 | Даже это решение не является удовлетворительным, так как последовательность приближений 331 | сходится довольно медленно. Немного простой математики может помочь в этом. 332 | Элементы последовательности могут быть выражены как 333 | 334 | 335 | the right answer + an error term involving h 336 | 337 | and it can be shown theoretically that the error term is roughly proportional to 338 | a power of h, so that it gets smaller as h gets smaller. Let the right answer be A, 339 | and let the error term be B × hn . Since each approximation is computed using 340 | a value of h twice that used for the next one, any two successive approximations 341 | can be expressed as 342 | 343 | и можно показать теоретически, что error term примерно пропорционально 344 | степени h и становится меньше по мере уменьшения h. Пусть правильный ответ будет A, 345 | и пусть error term будет B × hn. Поскольку каждое приближение вычисляется с 346 | использованием значения h дважды и которое используется для вычисления следующего приближения, 347 | любые два последовательные приближения могу быть выражены как 348 | 349 | 350 | ai = A + B × 2n × hn 351 | and 352 | ai+1 = A + B × hn 353 | 354 | Now the error term can be eliminated. We conclude 355 | 356 | Теперь error term может быть устранен. Мы приходим к выводу, что 357 | 358 | 359 | A= (an+1 × 2n − an) / 2n − 1 360 | 361 | Of course, since the error term is only roughly a power of h this conclusion is 362 | also approximate, but it is a much better approximation. This improvement 363 | can be applied to all successive pairs of approximations using the function 364 | 365 | Конечно, так как error term является лишь примерно степенью h этот вывод 366 | также приблизителен, но это намного лучшее приближение. Это усовершенствование 367 | может быть применено ко всем последовательным парам приближений с помощью функции 368 | 369 | 370 | elimerror n (Cons a (Cons b rest)) 371 | = Cons ((b ∗ (2^n) − a)/(2^n − 1)) (elimerror n (Cons b rest)) 372 | 373 | Eliminating error terms from a sequence of approximations yields another sequence, which converges much more rapidly. 374 | One problem remains before we can use elimerror — we have to know the 375 | right value of n. This is difficult to predict in general but is easy to measure. 376 | It’s not difficult to show that the following function estimates it correctly, but 377 | we won’t include the proof here: 378 | 379 | Устранение error term из последовательности приближений дает другую последовательность, 380 | сходящуюся намного быстрее. 381 | Одна из проблем остается прежде чем мы сможем использовать elimerror - мы должны знать 382 | правильное значение n. Его трудно предсказать в общем случае, но легко измерить. 383 | Не трудно показать, что следующая функция подсчитывает его правильно, но 384 | мы не будем приводить здесь доказательство: 385 | 386 | 387 | order (Cons a (Cons b (Cons c rest))) 388 | = round (log2 ((a − c)/(b − c) − 1)) 389 | round x = x rounded to the nearest integer 390 | log2 x = the logarithm of x to the base 2 391 | 392 | Now a general function to improve a sequence of approximations can be defined: 393 | 394 | Теперь основная функция для улучшения последовательности приближений может быть определена: 395 | 396 | 397 | improve s = elimerror (order s) s 398 | 399 | The derivative of a function f can be computed more efficiently using improve, 400 | as follows: 401 | 402 | Используя улучшения производную функции f можно вычислить более эффективно 403 | следующим образом: 404 | 405 | 406 | within eps (improve (differentiate h0 f x)) 407 | 408 | The function improve works only on sequences of approximations that are computed using a parameter h, which is halved for each successive approximation. 409 | However, if it is applied to such a sequence its result is also such a sequence! 410 | This means that a sequence of approximations can be improved more than once. 411 | A different error term is eliminated each time, and the resulting sequences converge faster and faster. Hence one could compute a derivative very efficiently 412 | using 413 | 414 | Функция improve применяется только для нахождения последовательностей приближений, которые вычисляются с использованием параметра h, уменьшающимся вдвое для каждого последующего приближения. 415 | Тем не менее, если он применяется к такой последовательности ее результатом является 416 | такая же последовательность! 417 | Это означает, что последовательность приближений может быть улучшена более чем один раз. 418 | Разница error term уменьшается каждый раз, и полученные последовательности сходятся все быстрее и быстрее. Поэтому можно было бы очень эффективно вычислить производную 419 | с помощью 420 | 421 | 422 | within eps (improve (improve (improve (differentiate h0 f x)))) 423 | 424 | In numerical analysts’ terms, this is likely to be a fourth-order method, and it 425 | gives an accurate result very quickly. One could even define 426 | 427 | С точки зрения численного анализа это, вероятно, будет методом четвертого порядка, и 428 | дает точный результат очень быстро. Можно даже определить 429 | 430 | 431 | super s = map second (repeat improve s) 432 | second (Cons a (Cons b rest)) = b 433 | 434 | which uses repeat improve to get a sequence of more and more improved sequences of approximations and constructs a new sequence of approximations 435 | by taking the second approximation from each of the improved sequences (it 436 | turns out that the second one is the best one to take — it is more accurate than the first and doesn’t require any extra work to compute). This algorithm 437 | is really very sophisticated — it uses a better and better numerical method as 438 | more and more approximations are computed. One could compute derivatives 439 | very efficiently indeed with the program: 440 | 441 | что использует repeat improve для нахождения последовательности все более улучшенных последовательностей приближений и строит новую последовательность приближений, 442 | взяв второе приближение от каждой из улучшенных последовательностей (оказалось, 443 | что второе из них является лучшим вариантом - оно является более точным чем первое и не требует каких-либо дополнительных действий для вычисления). Этот алгоритм 444 | на самом деле очень сложный - он использует все лучший и лучший численный метод по мере того 445 | как все больше и больше приближений вычисляются. Действительно, можно было бы вычислить производные 446 | очень эффективно с помощью программы: 447 | 448 | 449 | within eps (super (differentiate h0 f x)) 450 | 451 | This is probably a case of using a sledgehammer to crack a nut, but the point 452 | is that even an algorithm as sophisticated as super is easily expressed when 453 | modularized using lazy evaluation. 454 | 455 | Возможно, это случай использования кувалды для колки ореха, но суть в том, 456 | что даже алгоритм, сложный как super, может быть легко выражен при разделении на модули 457 | с использованием ленивых вычислений. 458 | 459 | 460 | ## 4.3 Numerical Integration 461 | 462 | ## 4.3 Численное интегрирование 463 | 464 | 465 | The last example we will discuss in this section is numerical integration. The 466 | problem may be stated very simply: Given a real-valued function f of one real 467 | argument, and two points a and b, estimate the area under the curve that f 468 | describes between the points. The easiest way to estimate the area is to assume 469 | that f is nearly a straight line, in which case the area would be 470 | 471 | Последний пример, который мы рассмотрим в этой главе, это численное интегрирование. 472 | Проблема может быть сформулирована очень просто: дана вещественная функция f с одним вещественным 473 | аргументом и две точки а и b, необходимо определить площадь под кривой, которую описывает f 474 | между этими точками. Самый простой способ оценить площадь заключается в предположении, 475 | что f почти прямая линия, и в этом случае площадь будет 476 | 477 | 478 | easyintegrate f a b = (f a + f b) ∗ (b − a)/2 479 | 480 | Unfortunately this estimate is likely to be very inaccurate unless a and b are 481 | close together. A better estimate can be made by dividing the interval from a to 482 | b in two, estimating the area on each half, and adding the results. We can define 483 | a sequence of better and better approximations to the value of the integral by 484 | using the formula above for the first approximation, and then adding together 485 | better and better approximations to the integrals on each half to calculate the 486 | others. This sequence is computed by the function 487 | 488 | К сожалению, эта велична, вероятно, будет очень неточной, если a и b не находятся 489 | близко к друг другу. Более точная велична может быть определена путем деления интервала от a до 490 | b на две части, определения площади каждой половины и сложения этих результатов. 491 | Мы можем определить последовательность лучших и лучших приближений к значению интеграла 492 | используя формулу выше для первого приближения, а затем складыванием 493 | лучших и лучших приближений к интегралам каждой половины, чтобы вычислить 494 | другие. Эта последовательность вычисляется с помощью функции 495 | 496 | 497 | integrate f a b = Cons (easyintegrate f a b) 498 | (map addpair (zip2 (integrate f a mid ) 499 | (integrate f mid b))) 500 | where mid = (a + b)/2 501 | 502 | The function zip2 is another standard list-processing function. It takes two lists 503 | and returns a list of pairs, each pair consisting of corresponding elements of the 504 | two lists. Thus the first pair consists of the first element of the first list and the 505 | first element of the second, and so on. We can define zip2 by 506 | 507 | Функция zip2 еще одна стандартная функция обработки списков. Он принимает два списка 508 | и возвращает список пар, каждая пара состоит из соответствующих элементов 509 | двух списков. Таким образом, первая пара состоит из первого элемента первого списка и 510 | первого элемента второго, и так далее. Мы можем определить zip2 путем 511 | 512 | 513 | zip2 (Cons a s) (Cons b t) = Cons (a, b) (zip2 s t) 514 | 515 | In integrate, zip2 computes a list of pairs of corresponding approximations to 516 | the integrals on the two subintervals, and map addpair adds the elements of the 517 | pairs together to give a list of approximations to the original integral. 518 | Actually, this version of integrate is rather inefficient because it continually 519 | recomputes values of f . As written, easyintegrate evaluates f at a and at b, 520 | and then the recursive calls of integrate re-evaluate each of these. Also, (f mid) 521 | is evaluated in each recursive call. It is therefore preferable to use the following 522 | version, which never recomputes a value of f : 523 | 524 | При интегрировании, zip2 вычисляет список пар соответствующих приближений к 525 | интегралам по двум подынтервалам, и map addpair добавляет элементы 526 | пар вместе для формирования списка приближений исходного интеграла. 527 | На самом деле, эта версия интегрирования весьма неэффективна, потому что он постоянно 528 | пересчитывает значения f. Как написано, easyintegrate вычисляет f в точке а и в точке b, 529 | а затем рекурсивные вызовы интегрирования повторно вычисляет каждую из них. Кроме того, (f mid) 530 | овычисляется в каждом рекурсивном вызове. Поэтому предпочтительно использовать следующую 531 | версию, которая никогда не пересчитывает значение f: 532 | 533 | 534 | integrate f a b = integ f a b (f a) (f b) 535 | integ f a b f a f b = 536 | 537 | Cons ((f a + f b) ∗ (b − a)/2) 538 | map addpair(zip2 (integ f a m f a f m) 539 | (integ f m b f m f b))) 540 | where m = (a + b)/2 541 | fm = f m 542 | 543 | The function integrate computes an infinite list of better and better approximations to the integral, just as differentiate did in the section above. One can 544 | therefore just write down integration routines that integrate to any required 545 | accuracy, as in 546 | 547 | Функция интегрирования вычисляет бесконечный список лучших и лучших приближений к интегралу, так же как дифференцирование в предыдущем разделе. Поэтому можно 548 | просто записать процедуры интегрирования, которые интегрируют с любой требуемой 549 | точностью, см. ниже 550 | 551 | 552 | within eps (integrate f a b) 553 | relative eps (integrate f a b) 554 | 555 | This integration algorithm suffers from the same disadvantage as the first differentiation algorithm in the preceding subsection — it converges rather slowly. 556 | Once again, it can be improved. The first approximation in the sequence is 557 | computed (by easyintegrate) using only two points, with a separation of b − a. 558 | The second approximation also uses the midpoint, so that the separation between neighboring points is only (b − a)/2. The third approximation uses this 559 | method on each half-interval, so the separation between neighboring points is 560 | only (b − a)/4. Clearly the separation between neighboring points is halved 561 | between each approximation and the next. Taking this separation as h, the 562 | sequence is a candidate for improvement using the function improve defined 563 | in the preceding section. Therefore we can now write down quickly converging 564 | sequences of approximations to integrals, for example, 565 | 566 | Этот алгоритм интегрирования страдает от того же недостатка, как первый алгоритм дифференцирования в предыдущем разделе - он сходится довольно медленно. 567 | И снова он может быть улучшен. Первое приближение в последовательности 568 | вычисляется (используя easyintegrate) используя только две точки с расстоянием Ь - а. 569 | Второе приближение также использует среднюю точку, так что расстояние между соседними точками только 570 | (b − a)/2. Третье приближение использует этот 571 | метод на каждой половине интервала, так что расстояние между соседними точками 572 | только (b − a)/4. Очевидно, что расстояние между соседними точками уменьшается в два раза 573 | между каждым приближением и последующим. Принимая это расстояние как h, эта 574 | последовательность является кандидатом на улучшения с помощью функции улучшения, определенной 575 | в предыдущем разделе. Поэтому мы можем теперь записать быстро сходящиеся 576 | последовательности приближений к интегралам, например, 577 | 578 | 579 | super (integrate sin 0 4) 580 | and 581 | improve (integrate f 0 1) 582 | where f x = 1/(1 + x ∗ x) 583 | 584 | (This latter sequence is an eighth-order method for computing π/4. The second 585 | approximation, which requires only five evaluations of f to compute, is correct 586 | to five decimal places.) 587 | In this section we have taken a number of numerical algorithms and programmed them functionally, using lazy evaluation as glue to stick their parts 588 | together. Thanks to this, we have been able to modularize them in new ways, 589 | into generally useful functions such as within, relative, and improve. By combining these parts in various ways we have programmed some quite good numerical algorithms very simply and easily. 590 | 591 | (Последняя последовательность представляет собой метод восьмого порядка для вычисления π/4. Второе 592 | приближение, для вычисления которого требуется только пять выполнений f, корректно 593 | до пяти знаков после запятой.) 594 | В этом разделе мы применили ряд численных алгоритмов и запрограммировали их функционально, используя ленивые вычисления как клей, чтобы склеить их части вместе. Благодаря этому, мы смогли модуляризировать их по-новому, посредством в целом полезных функций, таких как within, relative и improve. Объединяя эти части различными способами, мы довольно хорошо запрограммировали некоторые численные алгоритмы весьма просто и легко. 595 | -------------------------------------------------------------------------------- /5-an-example-from-artificial-intelligence.md: -------------------------------------------------------------------------------- 1 | # 5. An Example from Artificial Intelligence 2 | 3 | # 5. Пример из области искусственного интеллекта 4 | 5 | We have argued that functional languages are powerful primarily because they 6 | provide two new kinds of glue: higher-order functions and lazy evaluation. In 7 | this section we take a larger example from Artificial Intelligence and show how 8 | it can be programmed quite simply using these two kinds of glue. 9 | The example we choose is the alpha-beta “heuristic”, an algorithm for estimating how good a position a game-player is in. The algorithm works by looking 10 | ahead to see how the game might develop, but it avoids pursuing unprofitable 11 | lines. 12 | 13 | Ранее мы утверждали, что функциональные языки являются мощными в первую очередь потому, что они 14 | обеспечивают два новых типа соединения: функции высших порядков и ленивые вычисления. 15 | В данной главе мы разберем более сложную задачу из области искусственного интеллекта и покажем, как 16 | достаточно просто она может быть запрограммирована с их помощью. 17 | Для примера мы выберем алгоритм альфа-бета-отсечения "эвристика", который позволяет определить насколько хороша позиция игрока в игре. Алгоритм пытается просчитать варианты развития игры, 18 | избегая неперспективных направлений. 19 | 20 | 21 | Let game positions be represented by objects of the type position. This type 22 | will vary from game to game, and we assume nothing about it. There must be 23 | some way of knowing what moves can be made from a position: Assume that 24 | there is a function, 25 | 26 | Пусть позиции в игре будут представлены объектами типа position. Этот тип 27 | будет варьироваться от игры к игре и мы пока ничего не знаем об этом. Должен быть 28 | какой-нибудь способ узнать какие ходы могут быть сделаны из некоторой position: 29 | предположим, что есть функция, 30 | 31 | moves :: position → listof position 32 | 33 | that takes a game-position as its argument and returns the list of all positions 34 | that can be reached from it in one move. As an example, Fig. 1 shows moves for 35 | a couple of positions in tic-tac-toe (noughts and crosses). This assumes that it 36 | is always possible to tell which player’s turn it is from a position. In tic-tac-toe 37 | this can be done by counting the ×s and Os; in a game like chess one would have 38 | to include the information explicitly in the type position. 39 | 40 | которая берет position в качестве аргумента и возвращает список всех position 41 | за один ход. В качестве примера на рис. 1 показаны ходы для 42 | нескольких position в tic-tac-toe (крестики и нолики). Из position всегда можно 43 | предположить какого игрока очередь ходить. В этой игре это может быть сделано 44 | путем подсчета количества крестиков и ноликов, а такой игре как шахматы можно было бы 45 | включить эту информацию непосредственно в тип position. 46 | 47 | 48 | Given the function moves, the first step is to build a game tree. This is a 49 | tree in which the nodes are labeled by positions, so that the children of a node 50 | are labeled with the positions that can be reached in one move from that node. 51 | That is, if a node is labeled with position p, then its children are labeled with 52 | the positions in (moves p). Game trees are not all finite: If it’s possible for a 53 | game to go on forever with neither side winning, its game tree is infinite. Game 54 | trees are exactly like the trees we discussed in Section 2 — each node has a 55 | 56 | Учитывая функцию ходов, первый шаг заключается в создании игрового дерева. Это 57 | дерево, в котором узлы помечены position так, что потомки некоторого узла 58 | помечены position, которые могут быть достигнуты за один ход из этого узла. 59 | То есть, если узел помечен position р, то его потомки помечены 60 | positions in (ходит p). Не все игровые деревья имеют предел: если возможно для 61 | игры продолжаться вечно без победы ни одной из сторон, то ее игровое дерево бесконечно. Игровые 62 | деревья точно такие же как деревья, которые мы обсуждали в главе 2 - каждый узел имеет 63 | 64 | 65 | Figure 1: moves for two positions in tic-tac-toe. 66 | Figure 2: Part of a game tree for tic-tac-toe. 67 | 68 | label (the position it represents) and a list of subnodes. We can therefore use 69 | the same datatype to represent them. 70 | 71 | метку (представляет position) и список подузлов. Поэтому мы можем использовать 72 | тот же тип данных для их представления. 73 | 74 | A game tree is built by repeated applications of moves. Starting from the 75 | root position, moves is used to generate the labels for the subtrees of the root. 76 | It is then used again to generate the subtrees of the subtrees and so on. This 77 | pattern of recursion can be expressed as a higher-order function, 78 | 79 | Игровое дерево выстраивается путем повторяющихся применений ходов. Начиная от 80 | корневой позиции, ходы используется для генерации меток для поддеревьев корня. 81 | Затем они используется снова для создания поддеревьев поддеревьев и так далее. Этот 82 | образец рекурсии может быть выражен в виде функции высшего порядка, 83 | 84 | 85 | reptree f a = Node a (map (reptree f ) (f a)) 86 | 87 | Using this function another can be defined which constructs a game tree from 88 | a particular position: 89 | 90 | С помощью этой функции можно определить другую функцию, которая строит игровое дерево от 91 | конкретного положения: 92 | 93 | gametree p = reptree moves p 94 | 95 | As an example, consider Fig. 2. The higher-order function used here (reptree) is 96 | analogous to the function repeat used to construct infinite lists in the preceding 97 | section. 98 | 99 | В качестве примера, рассмотрим рис. 2. Используемая здесь функция высшего порядка (reptree) 100 | является аналогичной функции, которая используется для построения бесконечных списков 101 | в предшествующем разделе. 102 | 103 | The alpha-beta algorithm looks ahead from a given position to see whether 104 | the game will develop favorably or unfavorably, but in order to do so it must be 105 | able to make a rough estimate of the value of a position without looking ahead. 106 | This “static evaluation” must be used at the limit of the look-ahead, and may 107 | be used to guide the algorithm earlier. The result of the static evaluation 108 | is a measure of the promise of a position from the computer’s point of view 109 | (assuming that the computer is playing the game against a human opponent). 110 | The larger the result, the better the position for the computer. The smaller the 111 | result, the worse the position. The simplest such function would return (say) 112 | 1 for positions where the computer has already won, −1 for positions where 113 | the computer has already lost, and 0 otherwise. In reality, the static evaluation 114 | function measures various things that make a position “look good”, for example 115 | material advantage and control of the center in chess. Assume that we have 116 | such a function, 117 | 118 | Альфа-бета алгоритм смотрит вперед от заданной позиции, чтобы понять, будет ли 119 | игра развиваться благоприятно или неблагоприятно, но для того чтобы сделать это он должен быть 120 | в состоянии сделать грубую оценку значения позиции, не глядя вперед. 121 | Эта "статическая оценка" должна быть применена на границе прогноза и может быть 122 | использована для ориентирования алгоритма на ранней стадии. Результат статической оценки 123 | является мерой перспективности состояния с точки зрения компьютера 124 | (при условии, что компьютер играет в игру против человека). 125 | Чем значительнее результат, тем лучше состояние для компьютера. Чем незначительнее 126 | результат, тем хуже состояние. В простейшем случае такая функция будет возвращать (скажем) 127 | 1 для состояния, где компьютер уже победил, -1 для состояния где 128 | компьютер уже проиграл, и 0 в противном случае. На самом же деле, функция статической оценки 129 | измеряет различные вещи, которые составляют состояние "выглядит хорошо", например, 130 | материальный перевес и контроль центра в шахматах. Предположим, что мы имеем 131 | такую функцию, 132 | 133 | 134 | static :: position → number 135 | 136 | Since a game tree is a (treeof position), it can be converted into a (treeof number) 137 | by the function (maptree static), which statically evaluates all the positions in 138 | the tree (which may be infinitely many). This uses the function maptree defined 139 | in Section 2. 140 | 141 | Поскольку игровое дерево является (treeof position), оно может быть превращено в 142 | (treeof number) с помощью функции (maptree static), которая статически оценивает все 143 | позиции в этом дереве (которых может быть бесконечно много). При этом используется 144 | функция maptree, определенная в главе 2. 145 | 146 | 147 | Given such a tree of static evaluations, what is the true value of the positions 148 | in it? In particular, what value should be ascribed to the root position? Not 149 | its static value, since this is only a rough guess. The value ascribed to a node 150 | must be determined from the true values of its subnodes. This can be done by 151 | assuming that each player makes the best moves possible. Remembering that 152 | a high value means a good position for the computer, it is clear that when it is 153 | the computer’s move from any position, it will choose the move leading to the 154 | subnode with the maximum true value. Similarly, the opponent will choose the 155 | move leading to the subnode with the minimum true value. Assuming that the 156 | computer and its opponent alternate turns, the true value of a node is computed 157 | by the function maximize if it is the computer’s turn and minimize if it is not: 158 | 159 | Учитывая такое дерево статических оценок, какова истинная ценность позиций 160 | в нем? В частности, какая величина должна быть назначена корневой позиции? Не 161 | ее статическое значение, так как это только грубое предположение. Величина, назначенная узлу, 162 | должна быть определена из истинных значений его подузлов. Это может быть сделано предположив, 163 | что каждый игрок делает лучшие ходы. Не забывая, что 164 | высокое значение означает хорошее состояние для компьютера, очевидно, что в свою очередь из любой 165 | позиции он выберет ход, ведущий в подузел с максимальным истинным значением. 166 | Аналогичным образом, противник выберет ход, ведущий в подузел с минимальным истинным значением. Если предположить, что компьютер и его противник чередуют ходы, истинное значение узла вычисляется 167 | функцией maximize в случае хода компьютера и minimize в противном случае: 168 | 169 | 170 | maximize (Node n sub) = max (map minimize sub) 171 | minimize (Node n sub) = min (map maximize sub) 172 | 173 | Here max and min are functions on lists of numbers that return the maximum 174 | and minimum of the list respectively. These definitions are not complete because 175 | they recurse forever — there is no base case. We must define the value of a node 176 | with no successors, and we take it to be the static evaluation of the node (its 177 | label). Therefore the static evaluation is used when either player has already 178 | won, or at the limit of look-ahead. The complete definitions of maximize and 179 | minimize are 180 | 181 | Здесь max и min это функции списков чисел, которые возвращают максимум 182 | и минимум списка соответственно. Эти определения не являются полными, потому что 183 | они всегда рекурсивны - не существует базового случая. Мы должны определить значение узла 184 | без каких-либо потомков, и мы принимаем, что это будет статическая оценка узла (его 185 | метка). Поэтому статическая оценка используется, когда один из игроков уже или 186 | выиграл, или на пределе прогноза. Полные определения maximize и 187 | minimize являются 188 | 189 | 190 | maximize (Node n Nil ) = n 191 | maximize (Node n sub) = max (map minimize sub) 192 | minimize (Node n Nil ) = n 193 | minimize (Node n sub) = max (map maximize sub) 194 | 195 | One could almost write a function at this stage that would take a position and 196 | return its true value. This would be: 197 | 198 | На данном этапе можно было бы фактически написать функцию, которая брала некоторую позицию и 199 | возвращала ее истинное значение. Это было бы: 200 | 201 | 202 | evaluate = maximize . maptree static . gametree 203 | 204 | There are two problems with this definition. First of all, it doesn’t work for 205 | infinite trees, because maximize keeps on recursing until it finds a node with 206 | no subtrees — an end to the tree. If there is no end then maximize will return 207 | no result. The second problem is related — even finite game trees (like the one 208 | for tic-tac-toe) can be very large indeed. It is unrealistic to try to evaluate the 209 | whole of the game tree — the search must be limited to the next few moves. 210 | This can be done by pruning the tree to a fixed depth, 211 | 212 | Есть две проблемы с этим определением. Прежде всего, это не работает для 213 | бесконечных деревьев, потому что maximize выполняется рекурсивно до тех пор, пока не найдет узел без 214 | поддеревьев - т.е. конец дерева. Если нет конца, то maximize не вернет 215 | результат. Вторая проблема связана с тем, что даже игровые деревья с концом (как для игры 216 | крестики-нолики) могут быть очень большими. Это нереально пытаться оценить 217 | все игровое дерево - поиск должен быть ограничен несколькими последующими ходами. 218 | Это может быть сделано путем отсечения дерева на фиксированную глубину, 219 | 220 | 221 | prune 0 (Node a x) 222 | 223 | = Node a Nil 224 | 225 | prune (n + 1) (Node a x) = Node a (map (prune n) x) 226 | 227 | The function (prune n) takes a tree and “cuts off” all nodes further than n from 228 | the root. If a game tree is pruned it forces maximize to use the static evaluation 229 | for nodes at depth n, instead of recursing further. The function evaluate can 230 | therefore be defined by 231 | 232 | Функция (prune n) принимает дерево и "отсекает" все узлы дальше чем n от 233 | корня. Если игровое дерево отсечено, то это вынуждает maximize вместо рекурсии 234 | использовать статическую оценку для узлов на глубине n. Поэтому функция evaluate может 235 | быть определена как 236 | 237 | 238 | evaluate = maximize . maptree static . prune 5 . gametree 239 | 240 | which looks (say) five moves ahead. 241 | 242 | которая ищет (скажем) на пять ходов вперед. 243 | 244 | Already in this development we have used higher-order functions and lazy 245 | evaluation. Higher-order functions reptree and maptree allow us to construct 246 | and manipulate game trees with ease. More importantly, lazy evaluation permits 247 | us to modularize evaluate in this way. Since gametree has a potentially infinite 248 | result, this program would never terminate without lazy evaluation. Instead of 249 | writing 250 | 251 | Уже на этой стадии мы использовали функции высшего порядка и ленивые 252 | вычисления. Функции высшего порядка reptree и maptree позволяют нам с легкостью строить 253 | и манипулировать игровыми деревьями. Что еще более важно, таким образом ленивые вычисления 254 | позволяют нам модуляризировать вычисдения. Так как gametree имеет потенциально бесконечный 255 | результат, эта программа никогда не закончится без ленивых вычислений. Вместо этого 256 | напишем 257 | 258 | 259 | prune 5 . gametree 260 | 261 | we would have to fold these two functions together into one that constructed 262 | only the first five levels of the tree. Worse, even the first five levels may be too 263 | large to be held in memory at one time. In the program we have written, the 264 | function 265 | 266 | мы должны были бы сложить вместе две эти функции в одну, которая построит 267 | только первые пять уровней дерева. Хуже того, даже первые пять уровней могут занимать слишком 268 | много памяти в одно время. В программе, которую мы написали, 269 | функция 270 | 271 | 272 | maptree static . prune 5 . gametree 273 | 274 | constructs parts of the tree only as maximize requires them. Since each part 275 | can be thrown away (reclaimed by the garbage collector) as soon as maximize 276 | has finished with it, the whole tree is never resident in memory. Only a small 277 | part of the tree is stored at a time. The lazy program is therefore efficient. 278 | This efficiency depends on an interaction between maximize (the last function 279 | in the chain of compositions) and gametree (the first); without lazy evaluation, 280 | therefore, it could be achieved only by folding all the functions in the chain 281 | together into one big one. This would be a drastic reduction in modularity, 282 | but it is what is usually done. We can make improvements to this evaluation 283 | algorithm by tinkering with each part; this is relatively easy. A conventional 284 | programmer must modify the entire program as a unit, which is much harder. 285 | So far we have described only simple minimaxing. The heart of the alphabeta algorithm is the observation that one can often compute the value returned 286 | by maximize or minimize without looking at the whole tree. Consider the tree: 287 | 288 | конструирует части дерева как только maximize нуждается в них. Как только maximize 289 | завершает работу с каждой частью и она может быть отброшена (утилизирована сборщиком мусора) 290 | все дерево никогда не находится в памяти. Только небольшая 291 | часть дерева хранится одновременно. Поэтому ленивая программа эффективна. 292 | Эта эффективность зависит от взаимодействия между maximize (последняя функция 293 | в цепи композиций) и gametree (the first). Следовательно, без ленивых вычислений 294 | это может быть достигнуто только путем складывания всех функций вместе по цепочке 295 | в одну большую. Это было бы резкое снижение модульности, 296 | но так это обычно и делается. Мы можем внести улучшения в этот 297 | алгоритм вычисления повозившись с каждой частью - это относительно просто. Обычный же 298 | программист должен изменить всю программу, что гораздо сложнее. 299 | До сих пор мы описали только простые minimaxing. Суть алгоритма альфа-бета является наблюдение, 300 | что часто можно вычислить значение, возвращаемое 301 | maximize или minimize без прохода по всему дереву. Рассмотрим следующее дерево: 302 | 303 | 304 | *image* 305 | 306 | Strangely enough, it is unnecessary to know the value of the question mark 307 | in order to evaluate the tree. The left minimum evaluates to 1, but the right 308 | minimum clearly evaluates to something at most 0. Therefore the maximum of 309 | the two minima must be 1. This observation can be generalized and built into 310 | maximize and minimize. 311 | 312 | Как ни странно, нет необходимости знать значение знака вопроса 313 | для того, чтобы оценить дерево. Левый минимум принимает значение 1, а правый 314 | минимум несомненно оценивает что-то не более 0. Поэтому максимум 315 | двух минимумов должен быть равен 1. Это наблюдение можно обобщить и встроить в 316 | maximize и minimize. 317 | 318 | The first step is to separate maximize into an application of max to a list 319 | of numbers; that is, we decompose maximize as 320 | 321 | Первый шаг заключается в разделении maximize кв приложении max к списку 322 | чисел; то есть, мы декомпозируем maximize, как 323 | 324 | maximize = max . maximize0 325 | 326 | (We decompose minimize in a similar way. Since minimize and maximize are 327 | entirely symmetrical we shall discuss maximize and assume that minimize is 328 | treated similarly.) Once decomposed in this way, maximize can use minimize0 , 329 | rather than minimize itself, to discover which numbers minimize would take 330 | the minimum of. It may then be able to discard some of the numbers without 331 | looking at them. Thanks to lazy evaluation, if maximize doesn’t look at all of 332 | the list of numbers, some of them will not be computed, with a potential saving 333 | in computer time. 334 | 335 | (Мы декомпозируем minimize подобным же образом. Так как minimize и maximize являются 336 | полностью симметричными мы обсудим maximize и предположим, что minimize 337 | разбирается аналогично). После декомпозиции maximize может использовать minimize0, 338 | чтобы выяснить какие числа минимизировать для нахождения минимального из них. 339 | Затем она может быть в состоянии отказаться от некоторых из этих чисел без 340 | их проверок. Если maximize не проверяет весь список чисел, то благодаря ленивым вычислениям 341 | некоторые из них не будут вычисляться, что приведет к потенциальной экономии 342 | машинного времени. 343 | 344 | It’s easy to “factor out” max from the definition of maximize, giving 345 | 346 | Легко "вынести за скобки" максимум из определения maximize, давая 347 | 348 | maximize0 (Node n Nil ) = Cons n Nil 349 | maximize0 (Node n l ) 350 | 351 | = map minimize l 352 | = map (min . minimize0 ) l 353 | = map min (map minimize0 l ) 354 | = mapmin (map minimize0 l ) 355 | where mapmin = map min 356 | 357 | Since minimize0 returns a list of numbers, the minimum of which is the result of 358 | minimize, (map minimize0 l) returns a list of lists of numbers, and maximize0 359 | should return a list of those lists’ minima. Only the maximum of this list 360 | matters, however. We shall define a new version of mapmin that omits the 361 | minima of lists whose minimum doesn’t matter. 362 | 363 | Так как minimize0 возвращает список чисел, минимум которого является результатом 364 | сminimize, (map minimize0 l) возвращает список списков чисел, и maximize0 365 | должна возвращать список минимумов этих списков. Однако, важен только максимум этого списка. 366 | Мы определим новую версию mapmin, которая не включает 367 | минимумы списков чьи минимумы не имеет значения. 368 | 369 | 370 | mapmin (Cons nums rest) 371 | = Cons (min nums) (omit (min nums) rest) 372 | 373 | The function omit is passed a “potential maximum” — the largest minimum 374 | seen so far — and omits any minima that are less than this: 375 | 376 | Функция omit пропускает "возможный максимум" - наибольший минимум 377 | рассматриваемый до сих пор - и не включает любые минимумы, которые менше чем это: 378 | 379 | omit pot Nil = Nil 380 | omit pot (Cons nums rest) 381 | = omit pot rest, 382 | 383 | if minleq nums pot 384 | 385 | = Cons (min nums) (omit (min nums) rest), otherwise 386 | 387 | The function minleq takes a list of numbers and a potential maximum, and it 388 | returns True if the minimum of the list of numbers does not exceed the potential 389 | maximum. To do this, it does not need to look at the entire list! If there is 390 | any element in the list less than or equal to the potential maximum, then the 391 | minimum of the list is sure to be. All elements after this particular one are 392 | irrelevant — they are like the question mark in the example above. Therefore 393 | 394 | Функция minleq принимает список чисел и возможный максимум и 395 | возвращает True если минимум списка чисел не превосходит возможный 396 | максимум. Для этого ей не нужно проверять весь список! Если там есть 397 | любой элемент в списке меньший или равный возможному максимуму, то 398 | минимум списка обязательно будет. Все элементы после конкретно этого 399 | не важны - они похожи на знак вопроса в приведенном выше примере. Следовательно 400 | 401 | 402 | minleq can be defined by 403 | minleq Nil pot = F alse 404 | minleq (Cons n rest) pot = True, 405 | = minleq rest pot, 406 | 407 | if n ≤ pot 408 | otherwise 409 | 410 | Having defined maximize0 and minimize0 in this way it is simple to write a 411 | new evaluator: 412 | 413 | Определив таким образом maximize0 и minimize0 будет просто написать 414 | новый оценщик: 415 | 416 | evaluate = max . maximize0 . maptree static . prune 8 . gametree 417 | 418 | Thanks to lazy evaluation, the fact that maximize0 looks at less of the tree 419 | means that the whole program runs more efficiently, just as the fact that prune 420 | looks at only part of an infinite tree enables the program to terminate. The 421 | optimizations in maximize0 , although fairly simple, can have a dramatic effect 422 | on the speed of evaluation and so can allow the evaluator to look further ahead. 423 | Other optimizations can be made to the evaluator. For example, the alphabeta algorithm just described works best if the best moves are considered first, 424 | since if one has found a very good move then there is no need to consider worse 425 | moves, other than to demonstrate that the opponent has at least one good reply 426 | to them. One might therefore wish to sort the subtrees at each node, putting 427 | those with the highest values first when it is the computer’s move and those 428 | with the lowest values first when it is not. This can be done with the function 429 | 430 | Благодаря ленивым вычислениям тот факт, что maximize0 проверяет меньшую часть дерева 431 | означает, что вся программа работает более эффективно, так же как и то, что prune 432 | проверяет только часть бесконечного дерева, позволяющее программе завершиться. 433 | Оптимизации в maximize0, хотя и довольно простой, могут иметь драматические последствия 434 | по скорости вычисления и поэтому может позволить оценщику проверять дальше. 435 | К оценщику можно применить другие оптимизации. Например, только что описанный алгоритм алфа-бета 436 | работает наилучшим образом если самый удачный ход рассмотрен первым. Как только такой ход найден, 437 | то нет необходимости рассматривать менее удачные ходы, кроме как для демонстрации что у оппонента 438 | есть один хороший ответ на них. 439 | Поэтому можно было бы отсортировать поддеревья в каждом узле: во время хода компьтера переместить 440 | самымые большие значения на первые места и переместить самымые маленькие значения на 441 | первые места во время хода оппонента. Это может быть сделано с помощью функции 442 | 443 | highfirst (Node n sub) = Node n (sort higher (map lowfirst sub)) 444 | lowfirst (Node n sub) = Node n (sort (not . higher) (map highfirst sub)) 445 | higher (Node n1 sub1) (Node n2 sub2) = n1 > n2 446 | 447 | where sort is a general-purpose sorting function. The evaluator would now be 448 | defined by 449 | 450 | где sort является функцией сортировки общего назначения. Оценщик будет теперь 451 | определяется 452 | 453 | 454 | evaluate 455 | = max . maximize0 . highfirst . maptree static . prune 8 . gametree 456 | 457 | One might regard it as sufficient to consider only the three best moves for the 458 | computer or the opponent, in order to restrict the search. To program this, it 459 | is necessary only to replace highfirst with (taketree 3 . highfirst), where 460 | 461 | С тем чтобы ограничить поиск можно было бы относительно этого рассмотреть только три лучших хода для компьютера или противника. Чтобы запрограммировать это, 462 | необходимо только заменить highfirst с (taketree 3 . highfirst), где 463 | 464 | 465 | taketree n = foldtree (nodett n) Cons Nil 466 | nodett n label sub = Node label (take n sub) 467 | 468 | The function taketree replaces all the nodes in a tree with nodes that have at 469 | most n subnodes, using the function (take n), which returns the first n elements 470 | of a list (or fewer if the list is shorter than n). 471 | Another improvement is to refine the pruning. The program above looks 472 | ahead a fixed depth even if the position is very dynamic — it may decide to 473 | look no further than a position in which the queen is threated in chess, for 474 | example. It’s usual to define certain “dynamic” positions and not to allow lookahead to stop in one of these. Assuming a function dynamic that recognizes 475 | such positions, we need only add one equation to prune to do this: 476 | 477 | Функция taketree заменяет все узлы в дереве с узлами, которые имеют в 478 | большинство n подузлов, используя функцию (take n), которая возвращает первые n элементов 479 | из списка (или меньше если список короче чем n). 480 | Еще одним усовершенствованием является уточнение отсечения. Программа выше проверяет 481 | вперед на фиксированную глубину, даже если позиция очень динамична - она может принять решение 482 | проверять не дальше чем позиция, в котором ферзь под угрозой, в шахматах например. 483 | Обычно определяются такие четкие "динамические" позиции и не допускается предпроверка, чтобы не остановиться на одной из них. Предполагая что динамическая функция распознает 484 | такие позиции нам нужно добавить только одно уравнение к prune чтобы сделать это: 485 | 486 | 487 | prune 0 (Node pos sub) 488 | = Node pos (map (prune 0) sub), if dynamic pos 489 | 490 | Making such changes is easy in a program as modular as this one. As we 491 | remarked above, since the program depends crucially for its efficiency on an 492 | interaction between maximize, the last function in the chain, and gametree, 493 | the first, in the absence of lazy evaluation it could be written only as a monolithic 494 | program. Such a programs are hard to write, hard to modify, and very hard to 495 | understand. 496 | 497 | Легко делать эти изменения в таких же модульных программах как эта. Как мы 498 | отмечали выше, как только эффективность программы в решающей степени зависит от 499 | взаимодействия между maximize - последней функции в цепи, и gametree - 500 | первой, в отсутствие ленивых вычислениий она могла бы быть написана только как монолитная 501 | программа. Такие программы трудно писать, трудно модифицировать и очень трудно 502 | понимать. 503 | -------------------------------------------------------------------------------- /6-conclusion.md: -------------------------------------------------------------------------------- 1 | # 6. Conclusion 2 | 3 | In this paper, we’ve argued that modularity is the key to successful programming. Languages that aim to improve productivity must support modular programming well. But new scope rules and mechanisms for separate compilation 4 | are not enough — modularity means more than modules. Our ability to decompose a problem into parts depends directly on our ability to glue solutions 5 | together. To support modular programming, a language must provide good 6 | glue. Functional programming languages provide two new kinds of glue — 7 | higher-order functions and lazy evaluation. Using these glues one can modularize programs in new and useful ways, and we’ve shown several examples of this. 8 | Smaller and more general modules can be reused more widely, easing subsequent 9 | programming. This explains why functional programs are so much smaller and 10 | easier to write than conventional ones. It also provides a target for functional 11 | programmers to aim at. If any part of a program is messy or complicated, the 12 | programmer should attempt to modularize it and to generalize the parts. He or 13 | she should expect to use higher-order functions and lazy evaluation as the tools 14 | for doing this. 15 | Of course, we are not the first to point out the power and elegance of higherorder functions and lazy evaluation. For example, Turner shows how both can 16 | be used to great advantage in a program for generating chemical structures [3]. 17 | Abelson and Sussman stress that streams (lazy lists) are a powerful tool for 18 | structuring programs [1]. Henderson has used streams to structure functional 19 | operating systems [2]. But perhaps we place more stress on functional programs’ 20 | modularity than previous authors. 21 | This paper is also relevant to the present controversy over lazy evaluation. 22 | Some believe that functional languages should be lazy; others believe they 23 | should not. Some compromise and provide only lazy lists, with a special syntax 24 | for constructing them (as, for example, in Scheme [1]). This paper provides 25 | further evidence that lazy evaluation is too important to be relegated to secondclass citizenship. It is perhaps the most powerful glue functional programmers 26 | possess. One should not obstruct access to such a vital tool. 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Why functional programming matters 2 | Переводим статью John Hughes "[Why Functional Programming Matters](http://worrydream.com/refs/Hughes-WhyFunctionalProgrammingMatters.pdf)" 3 | 4 | ##Contributing 5 | 6 | 1. Задачи на перевод висят в issues, можно приступать к любой 7 | 2. Перевод, исправления принимаются через pull request в любом объеме. К pr привязывайте issue. 8 | 3. Переводим абзацами, сохраняем билингву: 9 | 10 | Абзац на английском 11 | Абзац на русском 12 | 13 | 4. Если вы прочитали перевод и согласны с ним, допишите об этом в issue. Если не согласны - так же допишите в issue (а лучше сразу присылайте свою версию исправлений) 14 | -------------------------------------------------------------------------------- /abstract.md: -------------------------------------------------------------------------------- 1 | As software becomes more and more complex, it is more and more 2 | important to structure it well. Well-structured software is easy to write 3 | and to debug, and provides a collection of modules that can be reused 4 | to reduce future programming costs. In this paper we show that two features of functional languages in particular, higher-order functions and lazy 5 | evaluation, can contribute significantly to modularity. As examples, we 6 | manipulate lists and trees, program several numerical algorithms, and implement the alpha-beta heuristic (an algorithm from Artificial Intelligence 7 | used in game-playing programs). We conclude that since modularity is the 8 | key to successful programming, functional programming offers important 9 | advantages for software development. 10 | 11 | Поскольку програмное обеспечение становится все более сложным, большую важность приобретает грамотная структура приложения. 12 | Хорошо структурированные программы легко писать и отлаживать, создавая их из модулей, которые можно повторно использовать для снижения стоимости будущей разработки. 13 | В этой статье мы покажем, что две возможности функциональных языков, функции высшего порядка и ленивые вычисления, могут в значительной мере способствовать созданию модульного приложения. 14 | В качестве примеров мы выполним некоторые действия со списками и деревьями, запрограммируем несколько численных алгоритмов и реализуем альфа-бета эвристику (алгоритм из области искусственного интеллекта, применяемый в компьютерных играх). 15 | В заключении мы придем к выводу, что, поскольку модульность является основой успешной разработки, функциональное программирование дает сильные преимущества для реализации этой идеи. 16 | 17 | -------------------------------------------------------------------------------- /acknowledgments.md: -------------------------------------------------------------------------------- 1 | # Acknowledgments 2 | # Благодарности 3 | 4 | This paper owes much to many conversations with Phil Wadler and Richard 5 | Bird in the Programming Research Group at Oxford. Magnus Bondesson at 6 | Chalmers University, G¨ 7 | oteborg, pointed out a serious error in an earlier version 8 | of one of the numerical algorithms, and thereby prompted development of many 9 | of the others. Ham Richards and David Turner did a superb editorial job, 10 | including converting the notation to Miranda. This work was carried out with 11 | the support of a Research Fellowship from the U.K. Science and Engineering 12 | Research Council. 13 | 14 | Эта статья во многом обязана беседам с Филом Уодлером и Ричардом Бердом 15 | в Исследовательской Группе Программировании в Оксфорде. Магнус Бондессон из 16 | университета Чалмерса, Гётеборг, указал на серьезную ошибку в более ранней 17 | версии одного из численных алгоритмов, и тем самым предложил совершенствование 18 | многих других. Хэм Ричардс и Дэвид Тернер сделали превосходную редакционную 19 | работу, в том числе преобразование нотации к Миранда. 20 | Эта работа была выполнена при поддержке исследовательской стипендии со стороны 21 | Исследовательского Совета Науки и Инженерии Великобритании. 22 | -------------------------------------------------------------------------------- /engRusProgrammersDictionary.md: -------------------------------------------------------------------------------- 1 | # English-Russian terms and expressions in programming 2 | 3 | 4 | in strict synchronization: в строгой последовательности 5 | 6 | 7 | gluing functions together: композиция функций 8 | 9 | 10 | gluing programs together: соединение программ вместе 11 | 12 | 13 | lazy evaluation: ленивые (отложенные) вычисления 14 | 15 | 16 | a tolerance (eps): точность, допуск (машинный эпсилон) 17 | 18 | 19 | higher-order function: функция высшего порядка 20 | 21 | 22 | lambda calculus: лямбда-исчисление 23 | 24 | 25 | anonymous functions: анонимные функции 26 | 27 | 28 | recursive function: рекурсивная функция 29 | 30 | 31 | referential transparency: ссылочная прозрачность 32 | --------------------------------------------------------------------------------