Entry tags:
Haskell, рефакторинг
Писал тут код для генерации однотипных задачек; результат должен был отправляться в TeX'овский файл. По ходу дела прибежали дополнительные требования. Сначала попросили, чтобы ответы жили в отдельном файле, потом чисто текстовую версию, и, наконец, коэффициенты в csv. В итоге, поскольку все усилия были направлены на содержательную генерацию, в IO части получился ужасный копипастный монстр:
Поглядел я на всё это, и понял, что дело дрянь. Дав волю бунтующему эстетическому чувству, свернул каждую четверку в один вызов через списочно-монадические функции:
Теперь видна следующая проблема: число 4, как длина списков. Если приедут требования дополнительного выходного формата, придется менять код в трёх местах, неочевидным образом связанных друг с другом. Не лучше ли передавать пары (имя файла, функция-генератор содержимого для него) в нужном количестве, то есть использовать в качестве аргументов функции список таких пар? Сказано -- сделано:
-- исходная версия doLinearProg fileTXT fileTeX fileAns fileDat = do -- создали/открыли файлы handleTXT <- openFile fileTXT AppendMode handleTeX <- openFile fileTeX AppendMode handleAns <- openFile fileAns AppendMode handleDat <- openFile fileDat AppendMode -- установили кодировку hSetEncoding handleTXT utf8_bom hSetEncoding handleTeX utf8_bom hSetEncoding handleAns utf8_bom hSetEncoding handleDat utf8_bom -- нагенерили список из 30 случайно-неслучайных структур lst <- replicateM 30 gen_pd -- забацали на основе каждой структуры текст задачи (taskXXX) -- и склеили все 30 задач в один текст с нумерацией (genText) let txt = genText taskTXT lst let tex = genText taskTeX lst let ans = genText answ lst let dat = genText taskDAT lst -- записали в файлы hPutStr handleTXT txt hPutStr handleTeX tex hPutStr handleAns ans hPutStr handleDat dat -- закрыли хэндлы hClose handleTXT hClose handleTeX hClose handleAns hClose handleDat
Поглядел я на всё это, и понял, что дело дрянь. Дав волю бунтующему эстетическому чувству, свернул каждую четверку в один вызов через списочно-монадические функции:
doLinearProg fileTXT fileTeX fileAns fileDat = do handles <- zipWithM openFile [fileTXT, fileTeX, fileAns, fileDat] (replicate 4 AppendMode) zipWithM hSetEncoding handles (replicate 4 utf8_bom) lst <- replicateM 30 gen_pd let txts = zipWith genText [taskTXT,taskTeX,answ,taskDAT] (replicate 4 lst) zipWithM hPutStr handles txts mapM hClose handlesА потом ещё чуть-чуть поморщился и избавился от дурацких replicate 4:
doLinearProg fileTXT fileTeX fileAns fileDat = do handles <- mapM (openFile `flip` AppendMode) [fileTXT, fileTeX, fileAns, fileDat] mapM (hSetEncoding `flip` utf8_bom) handles lst <- replicateM 30 gen_pd let txts = map (genText `flip` lst) [taskTXT, taskTeX, answ, taskDAT] zipWithM hPutStr handles txts mapM hClose handlesИспользование flip для частичного применения не первого, а второго аргумента функции -- полезная в хозяйстве идиома:
f `flip` y = \x -> f x y
Теперь видна следующая проблема: число 4, как длина списков. Если приедут требования дополнительного выходного формата, придется менять код в трёх местах, неочевидным образом связанных друг с другом. Не лучше ли передавать пары (имя файла, функция-генератор содержимого для него) в нужном количестве, то есть использовать в качестве аргументов функции список таких пар? Сказано -- сделано:
doLinearProg fNmsCGnsPairs = do let fileNames = map fst fNmsCGnsPairs let contentGens = map snd fNmsCGnsPairs handles <- mapM (openFile `flip` AppendMode) fileNames mapM (hSetEncoding `flip` utf8_bom) handles lst <- replicateM 30 gen_pd let txts = map (genText `flip` lst) contentGens zipWithM hPutStr handles txts mapM hClose handlesВы можете сделать мне замечание насчет числа 30. Я с ним полностью согласен, это число вытащено в окончательной версии в качестве параметра doLinearProg. Вызов её в итоге выглядит так
main = doLinearProg 30 [("res.txt", taskTXT), ("res.tex", taskTeX), ("res_answ.txt", answ), ("res_dat.csv", taskDAT)]
no subject
no subject