Немного о применении Хаскеля.

Nov 17, 2007 17:14

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

В течении своей жизни программист неоднократно переходит из одного состояния в другое, а иногда (и чаще всего) совмещает их оба, для разных задач применяя разные подходы. 1

Опытный программист превращается в неопытного сразу же после решения всех поставленных проблемой задач. Он садится за клавиатуру и монитор и сразу же берется за создание кода.

Как мы видим, неопытных программистов гораздо больше. Следовательно, они важнее.

Сам я отношусь к категории неопытных программистов. Поэтому и буду описывать ситуацию с этой точки зрения.

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

Не стоит ожидать ничего феерического, какой-то "ядерной физики." И задачи были относительно небольшие, и применялись они достаточно узко. Тут даже gui нет, не говоря о БД или чем еще.Задачи
Задачи можно побить на два класса: подсчет строк кода в исходниках и моделирование аппаратуры.

Программа подсчета строк кода понадобилась для оценки рабочего времени инженера на разработку какого-либо устройства. Вот мы хотим разработать что-то. Берется его прототип с http://opencores.org (их там много) и оценивается в строках кода. Исходя из оценки в 10-15 строк кода в час мы получим время работы инженера.

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

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

Моделирование аппаратуры представлено гораздо шире.

За "отчетный период" я написал порядка пяти моделей аппаратуры разного уровня подробности и разного уровня сложности. Сложность варьировалась от " сумматора Балина" для накопления суммы в присутствии задержки в сумматоре до небольшой мультипроцессорной системы (которая " вычислитель будущего").

Соответственно, уровень подробности тоже варьировался. В модели процессора MIPS было необходимо поддержать все достаточно точно, форматы команд, например, порядок байт, длину конвейера и объемы буферов. От сумматора Балина или моделей "вычислителей будущего" требовалось просто сказать, как влияет на время выполнения тот или иной параметр, подробности исполнения или представления программ не имели столь высокого значения.Цикл разработки
Цикл разработки практически во всех случаях представлял собой типичный цикл разработки неопытного программиста.

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

За небольшим исключением. Иногда приходилось подумать.

Тем не менее.

Вовсю использовался REPL.

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

В случае программ моделирования на вход интерпретатора подавался кусочек лога и соответствующий модуль подвергался изменениям и проверке.

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

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

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

В случае программы подсчета строк я выложил в наш Wiki .exe и исходники. К исходникам приложил небольшую инструкцию, как их собирать и как запускать программу. Заинтересованый в этой программе менеджер быстренько установил себе ghc (у него стоял red hat) и собрал себе свой вариант. Остальные руководители пользовались готовым экзешником под Виндовс. Даже я, когда мне надо было как-то оценить сроки работы моих временных подчиненных (я составлял планы).

Программа подсчета строк все равно выдавала ошибки то там, то тут, но общая точность подсчета лежала в пределах 2% от ручной обработки. Насколько я понял, основной пользователь пару раз ее проверил, а потом просто пользовался результатами, не обращая внимания на ошибки.Общие размышления
Несомненно положительным свойством Хаскеля является возможность удобной работы с сильно связанными модулями. Обычно связность выражается в знаниях о внутренностях и инвариантах значений (в других языках - объектов).

Обычный программист на обычном языке программирования старается сильно ограничить возвращаемые значения, часто не возвращая ничего, кроме nullable object pointer (аналог Maybe) или скалярного значения (ну, максимум, структуру-кортеж). В Хаскеле же можно возвращать гораздо более развернутые сообщения и правильно их обрабатывать.

Например, в модели MIPS на чтение и затем на исполнительные устройства передавалось сперва внутреннее представление команд, а потом специально созданное представление. Переделка потребовалась после введения очередного архитектурного изменения и заняла около двух недель.

Вот первоначальный типа вариант функции чтения:
mips_op_read ::
[MIPSI] -- список команд на выполнение.
-> [(R,MIPSWORD)] -- ответы от регистрового файла.
-> [(MIPSWORD,MIPSWORD,MIPSI)] -- команда и два операнда к ней.
-- Если у команды всего один операнд,
-- второй заполняется 0.А вот второй вариант.
mips_op_read ::
[ExecInstr (RegBypass R)] -- внутреннее представление команд, с индексами регистров.
-> [(R,MIPSWORD)] -- ответы от регистрового файла.
-> [ExecInstr (RegBypass MIPSWORD)] -- внутреннее представление,
-- параметризованное значениями
-- на местах регистров.
-- остались индексы регистров,
-- берущихся из закоротки.
-- после этого будет [ExecInstr MIPSWORD].Правда, пришлось подумать.

Автоматический вывод Show очень помогает в работе в сочетании с REPL.

Для приятного глазу вывода я с некоторых пор использую свой класс Pretty a where {pretty :: a -> Doc }. Получается лучшее из обоих миров.

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

Это никоим образом не отменяет дизайн. Если он нужен - а он временами нужен, - надо дизайнить. Но его значение ниже, и его можно применять по месту, а не заранее в качестве отдельного этапа деятельности.Примечания
1 До тех пор, пока не станет менеджером, тогда он навсегда фиксируется в положении неопытного программиста, считая все задачи легкими.
Previous post Next post
Up