Haskell, рефакторинг
Feb. 21st, 2010 07:51 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Писал тут код для генерации однотипных задачек; результат должен был отправляться в 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
Date: 2010-02-21 05:04 pm (UTC)no subject
Date: 2010-02-22 08:47 am (UTC)no subject
Date: 2010-02-21 05:50 pm (UTC)Но, конечно, в хаскеле всё выразительней.
Смешно как хаскельный flip напоминает фортовский swap.
no subject
Date: 2010-02-21 06:43 pm (UTC)(flip f) x y == f y x
no subject
Date: 2010-02-21 08:29 pm (UTC)(openFile `flip` AppendMode)
написал(`openFile` AppendMode)
. По-моему, проще.no subject
Date: 2010-02-21 08:31 pm (UTC)no subject
Date: 2010-02-21 09:18 pm (UTC)f `flip` x === `f` x
no subject
Date: 2010-02-21 09:30 pm (UTC)А === означает тождественно-претождественно? :-)
no subject
Date: 2010-02-21 10:31 pm (UTC)Просто (==) уже есть :)
no subject
Date: 2010-02-22 08:38 am (UTC)no subject
Date: 2010-02-22 08:39 am (UTC)no subject
Date: 2010-02-22 08:46 am (UTC)no subject
Date: 2010-02-22 08:50 am (UTC)(offtop)
Date: 2010-02-22 12:28 pm (UTC)Re: (offtop)
Date: 2010-02-22 01:42 pm (UTC)http://www.haskell.org/pipermail/cvs-libraries/2009-June/010890.html
(2,4) C UTF8 - нормально. Я принял решение прекратить работать под Windows с CP1251 и перейти на UTF8, где это только возможно (а возможно это в подавляющем большинстве нужных мне инструментов). А BOM мне нужен, чтобы всякий тупняк типа notepad.exe открывал всё в правильной кодировке.
(3) И не смотрел
no subject
Date: 2010-02-22 01:12 pm (UTC)Можно еще автоматизировать создание названий для файлов с результатами. Тогда получится что-то вроде
где Ext синоним для String и genText предполагается слегка переделаной. Осталось скомбинировать appendCustom с replicateM:
no subject
Date: 2010-02-22 01:48 pm (UTC)no subject
Date: 2010-02-22 01:59 pm (UTC)no subject
Date: 2010-02-22 03:28 pm (UTC)