Некоторые программисты, использующие Haskell, знают, что есть такой его вариант как «литературный» (или «грамотный») Haskell. Чем же он отличается от обычного?
Да тем, что комментарии и код как бы меняются местами — большую значимость имеют сами комментарии, а код заключается в блоки, оформленные как многострочные или однострочные строчки с ремарками (т.е. комментариями).
Скорее всего, мое объяснение того, что называется «литературным» Haskell несколько коряво. Да это так! И поэтому как бы, я должен немного пояснить свои рассуждения.
В общем, один известный программист предложил когда-то концепцию так называемого «литературного программирования», в котором программный код дополняется подробным объяснением логики его работы на естественном языке. Для того, чтобы это было возможным, комментариям в исходном коде придается бóльшее значение, чем самому коду. Чтобы исходные коды не мешали нормальному восприятию комментариев, специальное оформление комментариев было удалено, а сам код подвергся заключению в специальные блоки, которые на этапе обработки извлекаются специальной программой и далее просто транслируются.
При чем тут Icon? Дело в том, что для Icon не существует «литературного» варианта, но его можно легко реализовать… Итак, код идеи:
global syms,lines,blocks,incl,cline procedure init() syms := 0 lines := 0 blocks := 0 incl := 0 cline := 0 end procedure include(l) local i,j,f,acc acc := "" every i := !l do { f := open(i,"r") every j := !f do { acc ||:= j || "\n" } close(f) } return acc end procedure recognize(line) local lex,pline pline := "" lex := split(line," ") case lex[1] of { ">" : pline ||:= line[3:0] "" : cline := 1 " " : { cline := 0 blocks +:= 1 } "$include" : { pline := include(lex[2:0]) incl +:= 1 } default : { if cline = 1 then { pline := line } } } return pline end procedure split(line,dlms) local w /dlms := ' \t' w := [] line ? repeat { tab(upto(~dlms)) put(w,tab(many(~dlms))) | break } return w end procedure process(fin,fout) local ifile,ofile,line,pline ifile := open(fin, "r") ofile := open(fout, "cw") while line := !ifile do { pline := recognize(line) if pline ~== "" then { if pline ~== "end" then write(ofile,pline) else write(ofile,pline||"\n") } syms +:= *line lines +:= 1 } close(ifile) close(ofile) end procedure stat() local s s := "Processed: " || syms || " symbol(s), " || lines || " line(s), " || blocks || " block(s), " || incl || " file inclusion(s)." return s end procedure literate(ifile,ofile) init() process(ifile,ofile) write(stat()) end procedure main(argv) if *argv ~= 2 then write("usage: literate infile outfile") else literate(argv[1],argv[2]) end
Как это работает: создаем файл-«затравку», в котором код размещаем либо после символа > (после которого идет пробел) или же помещаем его в блок <begin>…<end> (тэги размещаются на новой строке, после чего с новой строки идет код), скармливаем «затравку» этой программке и получаем нормальный код на Icon, правда без комментариев. Эта программка имеет два аргумента командной строки: имя входного файла и имя выходного файла.
Сама программа состоит из нескольких элементарных процедур: процедура-инициализатор (выставляет начальные значения количества обработанных символов, строк, блоков и количество включенных файлов, а также значение флага «начала-конца» блока (это переменная cline)), процедура — распознаватель элементов «литературного» Icon (опознает блоки, код и команду включения файлов), процедура разбивки строки по определенному разделителю (в нашем случае, это пробел) на список элементов (этот список используется внутри процедуры — распознавателя). Но и эти процедуры играют вспомогательную роль, основным же элементом программы являются процедуры process и stat.
Первая процедура проходит исходный файл, применяя к нему вспомогательные процедуры, и генерирует новый файл избавленный от всего лишнего. Вторая процедура выдает краткую статистику по количеству обработанных единиц входного файла. Финальная процедура literate служит контейнером для объединения всех упомянутых функциональных блоков в единую систему. Как видно, все это довольно просто.
В заключение я показываю пример «литературного кода» и расскажу про кое-какую интересную штучку, вмонтированную в этот простейший «препроцессор»:
this is factorial example > procedure fact(x) > if x < 2 then return 1 else return x * fact(x - 1) iterative example > procedure factorial(x) > local p,i > p := 1 > every i := 1 to x do { > p *= i > } > return p > end
Результирующий файл (как вы уже догадались) будет выглядеть так:
procedure fact(x) if x < 2 then return 1 else return x * fact(x - 1) end procedure factorial(x) local p,i p := 1 every i := 1 to x do { p *= i } return p end
Указав во входном файле директиву $include file.icn, можно на место этой директивы поместить весь код находящийся в файле file.icn (разумеется, можно включить несколько файлов, указав пути к ним через пробел). Однако, стоит помнить о том, что код для вставки не должен быть "литературным", поскольку не подвергается обработке этой программкой.