forked from rigidus/onlisp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
04.01-Birth-of-a-Utility.txt
211 lines (171 loc) · 14.9 KB
/
04.01-Birth-of-a-Utility.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
In its simplest form, bottom-up programming means second-guessing whoever
designed your Lisp. At the same time as you write your program, you also add to
Lisp new operators which make your program easy to write. These new operators
are called utilities.
В простейшем виде, программирование снизу вверх подразумевает попытку наебать
создателей Lisp. FIXME. В то же время, когда вы пишите вашу программу вы так
же добавляете в Lisp новые операторы которые делают вашу программу более
простой в написании. Эти новые операторы называются утилитами (стандартные
либы/API внутри программы)FIXME
The term “utility” has no precise definition. A piece of code can be called
a utility if it seems too small to be considered as a separate application, and too
general-purpose to be considered as part of a particular program. A database
program would not be a utility, for example, but a function which performed a
single operation on a list could be. Most utilities resemble the functions and macros
that Lisp has already. In fact, many of Common Lisp’s built-in operators began
life as utilities. The function remove-if-not, which collects all the elements of
a list satisfying some predicate, was defined by individual programmers for years
before it became a part of Common Lisp.
Термин `утилита` не имеет точного определения. Кусок кода может быть назван
утилитой, если он слишком маленький, чтобы быть отдельным приложением и при
этом слишком общеупотребителен, чтобы быть частью одной программы. Например,
база данных не может быть утилитой, но функция, которая производит некую
операцию над списком может. Большинство утилит напоминают функции и
макросы, которые уже есть в Lisp. Фактически, многие встроенные в CL операторы
начали свою жизнь как утилиты. Функция remove-if-not, которая возвращает все
элементы, удовлетворяющие некоторому условию, была определена программистами
за годы до того, как она стала частью CL.
Learning to write utilities would be better described as learning the habit of
writing them, rather than the technique of writing them. Bottom-up programming
means simultaneously writing a program and a programming language. To do this
well, you have to develop a fine sense of which operators a program is lacking.
You have to be able to look at a program and say, “Ah, what you really mean to
say is this.”
Обучение написанию утилит может быть лучше описано как привитие привычки писать
их, а не описание способов написания их. Программирование снизу вверх означает
одновременное написание программы и языка программирования. Для того, чтобы
сделать это хорошо, вы должны ясно представить, каких
операторов в программе не хватает. Вы должны быть способны взглянуть на программу
и сказать: `Блеать, то, что ты имел ввиду, заключается в таком-то
алгоритме!`
For example, suppose that nicknames is a function which takes a name
and builds a list of all the nicknames which could be derived from it. Given
this function, how do we collect all the nicknames yielded by a list of names?
Someone learning Lisp might write a function like:
Например, представьте что никнэйм - это функция которая получает имена и преобразует
их в список никнэймов. Имея эту функцию, как мы соберём все никнеймы, полученные из
списка имен? Некто изучающий Lisp может написать эту функцию подобным образом:
(defun all-nicknames (names)
(if (null names)
nil
(nconc (nicknames (car names))
(all-nicknames (cdr names)))))
A more experienced Lisp programmer can look at such a function and say “Ah,
what you really want is mapcan.” Then instead of having to define and call a
new function to find all the nicknames of a group of people, you can use a single
expression:
Более опытный Lisp-программист может посмотреть на подобную функцию и сказать `Блеать,
то что тебе действительно нужно это mapcan.` В результате вместо написания и
вызова новой функции нахождения всех никнеймов группы людей, ты сможешь
использовать одно выражение:
(mapcan #’nicknames people)
The definition of all-nicknames is reinventing the wheel. However, that’s not
all that’s wrong with it: it is also burying in a specific function something that
could be done by a general-purpose operator.
Определение all-nicknames это изобретение велосипеда. Кстати, это не единственный косяк
этой функции: она также прячет в специальной функции нечто, что может быть
сделано оператором общего назначения.
In this case the operator, mapcan, already exists. Anyone who knew about
mapcan would feel a little uncomfortable looking at all-nicknames. To be good
at bottom-up programming is to feel equally uncomfortable when the missing
operator is one which hasn’t been written yet. You must be able to say “what you
really want is x,” and at the same time, to know what x should be.
В таком случае оператор, mapcan, уже существует. Любой, кто знает о
mapcan чувствует себя некомфортно, смотря на all-nicknames. Хорошо программировать
снизу вверх значит чувствовать такой же дискомфорт, когда необходимый оператор
ещё не написан в стандартной библиотеке. Вы должны уметь сказать `то что тебе
действительно нужно, это X` и в тоже время знать, что из себя представляет X.
Lisp programming entails, among other things, spinning off new utilities as
you need them. The aim of this section is to show how such utilities are born.
Suppose that towns is a list of nearby towns, sorted from nearest to farthest, and
that bookshops is a function which returns a list of all the bookshops in a city. If
we want to find the nearest town which has any bookshops, and the bookshops in
it, we could begin with:
Программирование на Lisp, кроме всего прочего, влечёт за собой водоворот новых
утилит, создаваемых при первой же необходимости. Цель этого раздела - показать
как такие утилиты рождаются. Представим, что towns - список близлежащих
городов, отсортированных от ближнего к дальнему и bookshops - функция, которая
возвращает список всех книжных магазинов в городе. Если мы хотим найти ближайший
город, в котором есть хоть один книжный магазин, и вернуть полученную информацию
о городе и магазинах, мы можем начать с:
(let ((town (find-if #’bookshops towns)))
(values town (bookshops town)))
But this is a bit inelegant: when find-if finds an element for which bookshops
returns a non-nil value, the value is thrown away, only to be recomputed as soon
as find-if returns. If bookshops were an expensive call, this idiom would be
inefficient as well as ugly. To avoid unnecessary work, we could use the following
function instead:
Но это немного коряво: когда find-if находит элемент, для которого bookshops
возвращает не nil значение, значение выбрасывается наружу, а затем мы вновь
производим операцию bookshops. Если вызов bookshops требует больших ресурсов,
эта идиома будет неэффективна и убога. Для избежания такого, мы можем
использовать следующую функцию:
(defun find-books (towns)
(if (null towns)
nil
(let ((shops (bookshops (car towns))))
(if shops
(values (car towns) shops)
(find-books (cdr towns))))))
Then calling (find-books towns) would at least get us what we wanted with
no more computation than necessary. But wait—isn’t it likely that at some time in
the future we will want to do the same kind of search again? What we really want
here is a utility which combines find-if and some, returning both the successful
element, and the value returned by the test function. Such a utility could be defined
as:
Вызов (find-books towns) будет возвращать как минимум то, что нам надо, без
лишних расчётов. Но подождите, мы же наверняка в будущем опять захотим подобный
тип поиска? Да, то, что действительно хочется, так это утилита, которая,
совмещая find-if и нечто, возвращает искомый элемент и значение проверочной
функции. Вот как может выглядеть такая утилита:
(defun find2 (fn lst)
(if (null lst)
nil
(let ((val (funcall fn (car lst))))
(if val
(values (car lst) val)
(find2 fn (cdr lst))))))
Notice the similarity between find-books and find2. Indeed, the latter could
be described as the skeleton of the former. Now, using the new utility, we can
achieve our original aim with a single expression:
Необходимо отметить сходство между find-books и find2. Фактически, find2 можно
воспринимать как скелетон find-books. Сейчас, используя новую утилиту, мы можем
добиться нашей изначальной цели в одно выражение:
(find2 #’bookshops towns)
One of the unique characteristics of Lisp programming is the important role
of functions as arguments. This is part of why Lisp is well-adapted to bottom-up
programming. It’s easier to abstract out the bones of a function when you can
pass the flesh back as a functional argument.
Одно из уникальных свойств Lisp программирования состоит в важной роли
использования функции в качестве аргумента. Это часть причины, по которой Lisp
хорошо адаптирован к программированию снизу вверх. Проще написать каркас
функции, когда ты можешь передать часть начинки в неё в качестве
функции-аргумента.
Introductory programming courses teach early on that abstraction leads to less
duplication of effort. One of the first lessons is: don’t wire in behavior. For
example, instead of defining two functions which do the same thing but for one
or two constants, define a single function and pass the constants as arguments.
Вводные курсы программирования ранее учили, что абстрагирование позволяет
избежать дублирования кода. Один из первых уроков: не будьте прямолинейным
FIXME. Например, вместо определения двух функций, которые делают одно и тоже, но
отличаются одной-двумя константами, определите одну функцию и передавайте
константы как аргументы.
In Lisp we can carry this idea further, because we can pass whole functions as
arguments. In both of the previous examples we went from a specific function to
a more general function which took a functional argument. In the first case we
used the predefined mapcan and in the second we wrote a new utility, find2, but
the general principle is the same: instead of mixing the general and the specific,
define the general and pass the specific as an argument.
В Lisp мы можем перенести эту идею дальше, потому как в качестве аргумента мы
можем передавать не только данные, но и код (функции). В предыдущих
примерах мы проходим путь от конкретной функции к более общей, которая в качестве
аргумента получает другую функцию. В первом случае мы используем
предопределённый mapcan; во втором мы пишем новую утилиту, find2, но общий
принцип такой же: вместо смешивание общего и частного, определяем общее и
передаём частное в качестве аргумента.
When carefully applied, this principle yields noticeably more elegant programs.
It is not the only force driving bottom-up design, but it is a major one. Of
the 32 utilities defined in this chapter, 18 take functional arguments.
При аккуратном использовании этот принцип порождает более элегантные программы.
Это не единственная сила, поддерживающая архитектуру снизу вверх, но одна из основных.
Из 32 утилит, определённых в этой главе, 18 получают аргументом функцию.