Некоторые программисты, использующие 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 (разумеется, можно включить несколько файлов, указав пути к ним через пробел). Однако, стоит помнить о том, что код для вставки не должен быть "литературным", поскольку не подвергается обработке этой программкой.