Здравствуйте, Gajdalager, Вы писали:
G>Если придраться к терминам, то это не совсем цикл, а внутренний итератор, реализированный как конструкция языка... А это скорее ФП чем не ФП
Ммм, разве итераторы стали функциональными?
Вон на хаскеле их чтот ни разу не видел, наверное он нефункциональный?
Здравствуйте, Курилка, Вы писали:
К>Ммм, разве итераторы стали функциональными? К>Вон на хаскеле их чтот ни разу не видел, наверное он нефункциональный?
Как это, а вот это — [a] — что?
На императивных ленивые списки (потоки, последовательности, етц.) эмулируется итераторами, так что это очень даже функциональная фишка.
Здравствуйте, VladD2, Вы писали: VD>Все это я бы назвал тактическими преимуществами. Они очень приятны и важны. Но для полного превосходства нужно иметь стратегические преимущества — упрощение дизайна приложений.
А на мой взгляд упрощение дизайна в некоторых случаях достигается. Мне вот тут на днях надо было простенький веб-интерфейс сделать, и я решил сделать его на scheme. PLT-шный фреймворк показался удачным — и когда я решил узнать про него побольше, оказалось, это т.н. основанный на продолжениях (continuations based) веб-фреймворк. Фишка таких фреймворков в том, что вместо манипулирования состоянием, программист манипулирует континуацией. Т.е., например, отправляя юзеру форму, программист указывает функцию, которая получит данные формы. Получается вполне удобно.
Здравствуйте, Курилка, Вы писали:
К>Здравствуйте, Gajdalager, Вы писали:
G>>Если придраться к терминам, то это не совсем цикл, а внутренний итератор, реализированный как конструкция языка... А это скорее ФП чем не ФП
К>Ммм, разве итераторы стали функциональными? К>Вон на хаскеле их чтот ни разу не видел, наверное он нефункциональный?
ИМХО внутренний итератор как раз привносит функциональную парадигму, т.к. являет собой использование ФВП (в не-ФП языках эмулированных с помощью паттерна Command). По духу вполне функционально. И ф-я foreach — разве это не внутренний итератор (если использовать терминологию GoF)?
Ну а внешний итератор, то который next/hasNext функциональным вряд ли можно назвать, конечно
<< RSDN@Home 1.2.0 alpha 4 rev. 1128>>
Сейчас играет In Extremo — Singapur
VE>>Это что, императивно? А между прочим, пишет в файл.
Т>Раз пишет в файл, значит императивно. По определению.
Согласен. А здесь — у нас только функция evalIO_ императивна, или writeAll тоже?
{-# LANGUAGE GADTs #-}data IO_ a where
WriteToFile :: String -> IO_ ()
Sequence :: [IO_ ()] -> IO_ ()
-- etc
writeAll xs = Sequence (map WriteToFile xs)
evalIO_ :: IO_ a -> IO a
evalIO_ = ...
Вообще спор старый как мир, и по большому счету бессмысленный. Разница между императивным и декларативным — в голове наблюдателя. А наличие какой-то системы эффектов (одним из вариантов реализации которой являются монады — заметьте, я не утверждаю, что самым лучшим) — объективный факт, все остальное — спекуляции.
Здравствуйте, Трурль, Вы писали: Т>Раз пишет в файл, значит императивно. По определению.
А вот и нет. На вход мы получаем пустой файл, на выходе получаем новый — с данными. То, что гадкая императивная ОСь записывает новенький файл на место старого — уже не наша забота.
Здравствуйте, Mr.Cat, Вы писали:
MC>А вот и нет. На вход мы получаем пустой файл, на выходе получаем новый — с данными. То, что гадкая императивная ОСь записывает новенький файл на место старого — уже не наша забота.
Запись файла — это наблюдаемый побочный эффект, причем наблюдаемый не только из гадкой императивной оси, но из нашей непорочно чистой программы. Повторюсь, весь фокус — в типизации эффектов.
Здравствуйте, palm mute, Вы писали:
PM>Здравствуйте, Mr.Cat, Вы писали:
MC>>А вот и нет. На вход мы получаем пустой файл, на выходе получаем новый — с данными. То, что гадкая императивная ОСь записывает новенький файл на место старого — уже не наша забота.
PM>Запись файла — это наблюдаемый побочный эффект, причем наблюдаемый не только из гадкой императивной оси, но из нашей непорочно чистой программы. Повторюсь, весь фокус — в типизации эффектов.
Осторожно, сейчас вы опять с темы съедете Речь шла не о pure-functional и не о отсутствии side-effects, а о программировании в функциональной монере (с). Манера программирования всё же далека от теоретических изысков и подразумевает преимущественное использование неких практик — к ним чаще всего относят использование ФВП, замыкания, иммутабельные структуры и отсутствие side-effects. Однако это не значит, что если в программе есть хоть один side-effect, то она императивна. Опять таки, это спор о терминах, но!
def words = ["abba", "yield"];
foreach (group in words.GroupBy(w => w[0]))
File.WriteAllText($"$(group.Key).txt", $"..$group");
Я бы назвал эту программу скорее функциональной чем императивной. Есть у меня чёткое ощущение, что внутренний итератор был заимствован из функционального мира.
<< RSDN@Home 1.2.0 alpha 4 rev. 1128>>
Сейчас играет In Extremo — Mein Kind
Здравствуйте, Gajdalager, Вы писали:
G>Я бы назвал эту программу скорее функциональной чем императивной. Есть у меня чёткое ощущение, что внутренний итератор был заимствован из функционального мира.
Подобные споры возникают с завидной регулярностью именно из-за того, что ощущения у всех разные, и разница между императивным и декларативным находится в философской плоскости. А оффтопик в этой ветке начался значительно раньше моего сообщения.
Переделал я свой код. Вот что получилось.
Теперь у нас есть bigSort, которая не привязана к файлам и может сортировать большие списки (в условиях ограничений памяти).
a |> f = f a
-- сортирует длинный список
-- разбивает список на последовательные блоки, сортирует их, записывает во временные файлы, затем файлы читает и объединяет
bigSort :: (Read a, Ord a, Show a) => [a] -> IO [a]
bigSort xs = xs |> breakUp 10000 |> map sort |> writeChains >>= readChains >>= (merge >>> return)
-- сохраняет блоки во временные файлы, возвращает имена файлов, куда записал
writeChains chains = do
let tempFiles = [1..] |> map show |> map ("temp"++)
let writeChain' (filename, xs) = xs |> map show |> unlines |> writeFile filename >> return filename
mapM writeChain' (zip tempFiles chains)
-- читает блоки из временных файлов
readChains files = do
let readChain' file = readFile file >>= (lines >>> map read >>> return)
mapM readChain' files
-- разбивает список на части размером size
breakUp :: Int -> [a] -> [[a]]
breakUp size xs = let takeBlock ps = guard (null ps |> not) >> Just (splitAt size ps)
in unfoldr takeBlock xs
-- объединяет несколько упорядоченных списков в один упорядоченный список
merge :: (Ord a) => [[a]] -> [a]
merge [] = []
merge ss = foldl1 mergePair ss
mergePair [] ss = ss
mergePair ss [] = ss
mergePair (s1:ss1) (s2:ss2) | s1 <= s2 = s1 : mergePair ss1 (s2:ss2)
| otherwise = s2 : mergePair (s1:ss1) ss2
DM>1. Есть большой текстовый файл A (несколько гигов, заведомо не помещается в память), нужно сделать текстовый файл A1, содержащий все строки из А, но отсортированные в алфавитном порядке. Ограничение по памяти — 256 МБ.
main = do
args <- getArgs
let src = head args
let dst = src ++ "_sorted"
readFile src >>= (lines >>> bigSort) >>= (unlines >>> writeFile dst)
DM>2. Изменить первую программу так, чтобы будучи запущена с ключом -u она создавала файл А2, содержащий все уникальные строки из А в алфавитном порядке и количество повторений для каждой строки.
main2 = do
args <- getArgs
let src = head args
let dst = src ++ "_sorted"let dup = src ++ "_dup"let needDup = any (=="-u") args
sortedData <- readFile src >>= (lines >>> bigSort)
hdst <- openFile dst WriteMode
hdup <- openFile dup WriteMode
-- количество одинаковых строк и сами эти строкиlet gs = [(length g, head g) | g <- group sortedData]
-- пишем параллельно в sorted и duplet writeGroup (count, str) = do
replicateM_ count (hPutStrLn hdst str)
if needDup then hPutStrLn hdup (show count ++ ": " ++ str) else return ()
mapM_ writeGroup [(length g, head g) | g <- group sortedData]
hClose hdup
hClose hdst
Переделал я свой код. Вот что получилось.
Теперь у нас есть bigSort, которая не привязана к файлам и может сортировать большие списки (в условиях ограничений памяти).
a |> f = f a
-- сортирует длинный список
-- разбивает список на последовательные блоки, сортирует их, записывает во временные файлы, затем файлы читает и объединяет
bigSort :: (Read a, Ord a, Show a) => [a] -> IO [a]
bigSort xs = xs |> breakUp 10000 |> map sort |> writeChains >>= readChains >>= (merge >>> return)
-- сохраняет блоки во временные файлы, возвращает имена файлов, куда записал
writeChains chains = do
let tempFiles = [1..] |> map show |> map ("temp"++)
let writeChain' (filename, xs) = xs |> map show |> unlines |> writeFile filename >> return filename
mapM writeChain' (zip tempFiles chains)
-- читает блоки из временных файлов
readChains files = do
let readChain' file = readFile file >>= (lines >>> map read >>> return)
mapM readChain' files
-- разбивает список на части размером size
breakUp :: Int -> [a] -> [[a]]
breakUp size xs = let takeBlock ps = guard (null ps |> not) >> Just (splitAt size ps)
in unfoldr takeBlock xs
-- объединяет несколько упорядоченных списков в один упорядоченный список
merge :: (Ord a) => [[a]] -> [a]
merge [] = []
merge ss = foldl1 mergePair ss
mergePair [] ss = ss
mergePair ss [] = ss
mergePair (s1:ss1) (s2:ss2) | s1 <= s2 = s1 : mergePair ss1 (s2:ss2)
| otherwise = s2 : mergePair (s1:ss1) ss2
DM>1. Есть большой текстовый файл A (несколько гигов, заведомо не помещается в память), нужно сделать текстовый файл A1, содержащий все строки из А, но отсортированные в алфавитном порядке. Ограничение по памяти — 256 МБ.
main = do
args <- getArgs
let src = head args
let dst = src ++ "_sorted"
readFile src >>= (lines >>> bigSort) >>= (unlines >>> writeFile dst)
DM>2. Изменить первую программу так, чтобы будучи запущена с ключом -u она создавала файл А2, содержащий все уникальные строки из А в алфавитном порядке и количество повторений для каждой строки.
main2 = do
args <- getArgs
let src = head args
let dst = src ++ "_sorted"let dup = src ++ "_dup"let needDup = any (=="-u") args
sortedData <- readFile src >>= (lines >>> bigSort)
hdst <- openFile dst WriteMode
hdup <- openFile dup WriteMode
-- количество одинаковых строк и сами эти строкиlet gs = [(length g, head g) | g <- group sortedData]
-- пишем параллельно в sorted и duplet writeGroup (count, str) = do
replicateM_ count (hPutStrLn hdst str)
if needDup then hPutStrLn hdup (show count ++ ": " ++ str) else return ()
mapM_ writeGroup [(length g, head g) | g <- group sortedData]
hClose hdup
hClose hdst
Здравствуйте, BulatZiganshin, Вы писали:
BZ>если ограничивать число строк, а не объём занимаемой памяти — то нормально
main = do-- (Лениво) создаём отсортированные подсписки
sublists <- fmap sort1 getContents
-- Записываем их в файлы с именами 1..n
zipWithM_ writeFile (map show [1..]) sublists
-- Читаем (опять же лениво) содержимое всех ранее созданных файлов
sublists <- mapM (fmap lines readFile.show) [1..length sublists]
-- Сливаем их и записываем результат
putStr (unlines (mergesort' compare sublists)
BZ>на самом деле, даже если я ни в чём не ошибся — этот код будет держать все данные в памяти до победного. extra points — за изящное решение этой маааленькой проблемы
Я, вроде, разобрался в чем тут проблемы — все дело в length sublists. Из-за этого пропадает ленивость.
Если же переписать вот так, то все будет работать:
main = do-- (Лениво) создаём отсортированные подсписки
sublists <- fmap sort1 getContents
-- Записываем их в файлы с именами 1..ncount <- fmap length $ zipWithM writeFile (map show [1..]) sublists
-- Читаем (опять же лениво) содержимое всех ранее созданных файлов
sublists <- mapM (fmap lines readFile.show) [1..count]
-- Сливаем их и записываем результат
putStr (unlines (mergesort' compare sublists)
Всё таки для Haskell ограничения по памяти противопоказаны
Здравствуйте, Beam, Вы писали:
BZ>>на самом деле, даже если я ни в чём не ошибся — этот код будет держать все данные в памяти до победного. extra points — за изящное решение этой маааленькой проблемы
B>Я, вроде, разобрался в чем тут проблемы — все дело в length sublists. Из-за этого пропадает ленивость. B>Если же переписать вот так, то все будет работать:
Здравствуйте, BulatZiganshin, Вы писали:
BZ>ещё одна задача из моей практики. есть список файлов, каждый файл представлен структурой, содержащей поля ext (расширение файла), size (его размер) и другие, нам неинтересные. нужно разбить этот список на группы (солид-блоки) по критериям, описанным строкой вида [Nf][Mb][e], где N и M — некоторые числа. эта запись означает, что каждая группа может содержать не более N файлов, суммарным объёмом не более M байт, с одинаковым расширением. все три части этой строки необязательны — при их отсутствии соответствующий критерий не применяется
Окамл:
(*базовый вещи *)open ExtString;;
let (|>) x f = f x;;
let (>>) f g x = g (f x);;
(* структура с информацией о файле *)type file_t = { name : string; size : int; ext : string };;
(* структура-аккумулятор с текущим блоком файлов *)type accum_t = { files : file_t list; num : int; totalsize : int};;
let new_acc = { files = []; num = 0; totalsize = 0 };; (* пустой блок *)let accumulate acc file =
{ files = file::acc.files; num = acc.num + 1; totalsize = acc.totalsize + file.size };;
(* разбор строки параметров *)let parse_param_string str =
let numparam ch =
let rec loop i lst =
if i<0 then lst else
if str.[i]>='0' && str.[i]<='9'then loop (i-1) (str.[i] :: lst) else lst in
try Some (loop (String.index str ch - 1) [] |> String.implode |> int_of_string)
with Not_found -> None in
numparam 'f', numparam 'b', String.contains str 'e';;
(* основная функция. из списка файлов делает список списков файлов согласно параметрам из строки *)let make_blocks param_str file_list =
if param_str = ""then [file_list] else(* простая оптимизация, можно и без нее *)let num_opt, size_opt, ext_opt = parse_param_string param_str in(* разбираем параметры *)
(* создаем предикаты, говорящие можно ли поместить файл в текущий блок *)let always = (fun acc file -> true) in
let size_pred = Option.map_default (fun size_limit -> (fun a f-> a.totalsize + f.size <= size_limit)) always size_opt
and ext_pred = if ext_opt then (fun a f -> if a.num > 0 then (List.hd a.files).ext = f.ext else true) else always
and num_pred = Option.map_default (fun num_limit -> (fun a f-> a.num + 1 <= num_limit)) always num_opt in(* можно, если все три предиката не возражают *)let can_accum = (fun acc file -> List.for_all (fun pred-> pred acc file) [ext_pred; size_pred; num_pred]) in(* обработка одного файла: *)let process (acc, res_list) file =
if can_accum acc file then accumulate acc file, res_list
else accumulate new_acc file, (acc::res_list) in(* обработка всего списка: собираем новый список fold'ом, разворачиваем и убираем лишнее rev_map'ом *)let acc, reslst = List.fold_left process (new_acc, []) file_list in
acc::reslst |> List.rev_map (fun acc-> List.rev acc.files);;
Здравствуйте, D. Mon, Вы писали:
DM>Использованы только иммутабельные структуры и чистые функции.
отсутствие побочных эффектов, конечно, хорошо, так как облегчает отладку, но на мой взгляд это ещё не максимум того, что можно выжать из фп подхода. для меня фп стиль — это поток преобразований входных данных в выходные, в идеале без всяких вспомогательных переменных
в данном случае это может быть поток преобразования от строки критериев к *функции* разбиения на подсписки. и состоять он может из таких шагов:
1) разбить строку на отдельные критерии
2) преобразовать каждый из них в функцию разбиения на [ленивые] подсписки по одному этому критерию
3) скомбинировать эти функции в итоговую
и вообще задача тренинга, как я её себе ставил — помочь перейти от мышления, ориентированного на манипуляцию данными, к мышлению, ориентированному на манипуляцию функциями
Здравствуйте, BulatZiganshin, Вы писали:
BZ>2) преобразовать каждый из них в функцию разбиения на [ленивые] подсписки по одному этому критерию BZ>3) скомбинировать эти функции в итоговую
Хм, если они будут разбивать независимо друг от друга, получится плохо:
Пусть 1-я функция разбивает по количеству файлов, а вторая — по суммарному размеру. Тогда их разбиения могут выглядеть так:
1) 11112222333344445555
2) 11222233334444555567
Что будет результатом комбинирования?