Re[9]: Мастер-класс по ФП
От: Курилка Россия http://kirya.narod.ru/
Дата: 19.01.09 20:34
Оценка:
Здравствуйте, Gajdalager, Вы писали:

G>Если придраться к терминам, то это не совсем цикл, а внутренний итератор, реализированный как конструкция языка... А это скорее ФП чем не ФП


Ммм, разве итераторы стали функциональными?
Вон на хаскеле их чтот ни разу не видел, наверное он нефункциональный?
Re[10]: Мастер-класс по ФП
От: VoidEx  
Дата: 19.01.09 21:04
Оценка: -1
Здравствуйте, Курилка, Вы писали:

К>Ммм, разве итераторы стали функциональными?

К>Вон на хаскеле их чтот ни разу не видел, наверное он нефункциональный?
Как это, а вот это — [a] — что?
На императивных ленивые списки (потоки, последовательности, етц.) эмулируется итераторами, так что это очень даже функциональная фишка.
Re[4]: Мастер-класс по ФП
От: Mr.Cat  
Дата: 19.01.09 23:46
Оценка:
Здравствуйте, VladD2, Вы писали:
VD>Все это я бы назвал тактическими преимуществами. Они очень приятны и важны. Но для полного превосходства нужно иметь стратегические преимущества — упрощение дизайна приложений.

А на мой взгляд упрощение дизайна в некоторых случаях достигается. Мне вот тут на днях надо было простенький веб-интерфейс сделать, и я решил сделать его на scheme. PLT-шный фреймворк показался удачным — и когда я решил узнать про него побольше, оказалось, это т.н. основанный на продолжениях (continuations based) веб-фреймворк. Фишка таких фреймворков в том, что вместо манипулирования состоянием, программист манипулирует континуацией. Т.е., например, отправляя юзеру форму, программист указывает функцию, которая получит данные формы. Получается вполне удобно.
Re[10]: Мастер-класс по ФП
От: Gajdalager Украина  
Дата: 20.01.09 06:27
Оценка:
Здравствуйте, Курилка, Вы писали:

К>Здравствуйте, Gajdalager, Вы писали:


G>>Если придраться к терминам, то это не совсем цикл, а внутренний итератор, реализированный как конструкция языка... А это скорее ФП чем не ФП


К>Ммм, разве итераторы стали функциональными?

К>Вон на хаскеле их чтот ни разу не видел, наверное он нефункциональный?

ИМХО внутренний итератор как раз привносит функциональную парадигму, т.к. являет собой использование ФВП (в не-ФП языках эмулированных с помощью паттерна Command). По духу вполне функционально. И ф-я foreach — разве это не внутренний итератор (если использовать терминологию GoF)?
Ну а внешний итератор, то который next/hasNext функциональным вряд ли можно назвать, конечно
<< RSDN@Home 1.2.0 alpha 4 rev. 1128>>
Сейчас играет In Extremo — Singapur
Re[9]: Мастер-класс по ФП
От: Трурль  
Дата: 20.01.09 13:02
Оценка:
Здравствуйте, VoidEx, Вы писали:

VE>
VE>mapM_ writeToFile lst
VE>

VE>Это что, императивно? А между прочим, пишет в файл.

Раз пишет в файл, значит императивно. По определению.
Re[10]: Мастер-класс по ФП
От: Gajdalager Украина  
Дата: 20.01.09 13:20
Оценка:
Здравствуйте, Трурль, Вы писали:

Т>Здравствуйте, VoidEx, Вы писали:


Т>Раз пишет в файл, значит императивно. По определению.


Определение в студию Отсутствие side-эффектов не обязательно для программирования в функциональном стиле.
<< RSDN@Home 1.2.0 alpha 4 rev. 1128>>
Сейчас играет In Extremo — Nur Ihr Allein
Re[10]: Мастер-класс по ФП
От: palm mute  
Дата: 20.01.09 13:38
Оценка:
Здравствуйте, Трурль, Вы писали:

VE>>
VE>>mapM_ writeToFile lst
VE>>

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_ = ...


Вообще спор старый как мир, и по большому счету бессмысленный. Разница между императивным и декларативным — в голове наблюдателя. А наличие какой-то системы эффектов (одним из вариантов реализации которой являются монады — заметьте, я не утверждаю, что самым лучшим) — объективный факт, все остальное — спекуляции.
Re[10]: Мастер-класс по ФП
От: VoidEx  
Дата: 20.01.09 14:17
Оценка:
Здравствуйте, Трурль, Вы писали:

Т>Раз пишет в файл, значит императивно. По определению.

Забавно
Re[10]: Мастер-класс по ФП
От: Mr.Cat  
Дата: 20.01.09 14:54
Оценка:
Здравствуйте, Трурль, Вы писали:
Т>Раз пишет в файл, значит императивно. По определению.

А вот и нет. На вход мы получаем пустой файл, на выходе получаем новый — с данными. То, что гадкая императивная ОСь записывает новенький файл на место старого — уже не наша забота.
Re[11]: Мастер-класс по ФП
От: palm mute  
Дата: 20.01.09 14:57
Оценка:
Здравствуйте, Mr.Cat, Вы писали:

MC>А вот и нет. На вход мы получаем пустой файл, на выходе получаем новый — с данными. То, что гадкая императивная ОСь записывает новенький файл на место старого — уже не наша забота.


Запись файла — это наблюдаемый побочный эффект, причем наблюдаемый не только из гадкой императивной оси, но из нашей непорочно чистой программы. Повторюсь, весь фокус — в типизации эффектов.
Re[12]: Мастер-класс по ФП
От: Gajdalager Украина  
Дата: 20.01.09 15:39
Оценка:
Здравствуйте, 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
Re[13]: Мастер-класс по ФП
От: palm mute  
Дата: 20.01.09 15:49
Оценка: 1 (1) +2
Здравствуйте, Gajdalager, Вы писали:

G>Я бы назвал эту программу скорее функциональной чем императивной. Есть у меня чёткое ощущение, что внутренний итератор был заимствован из функционального мира.

Подобные споры возникают с завидной регулярностью именно из-за того, что ощущения у всех разные, и разница между императивным и декларативным находится в философской плоскости. А оффтопик в этой ветке начался значительно раньше моего сообщения.
Re[2]: Мастер-класс по ФП
От: Beam Россия  
Дата: 21.01.09 21:53
Оценка: 10 (1)
Переделал я свой код. Вот что получилось.
Теперь у нас есть 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 и dup
  let 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


Не очень красиво, зато укладываемся в ограничения
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Best regards, Буравчик
Re[2]: Мастер-класс по ФП
От: Beam Россия  
Дата: 21.01.09 22:58
Оценка:
Переделал я свой код. Вот что получилось.
Теперь у нас есть 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 и dup
  let 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


Не очень красиво, зато укладываемся в ограничения
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Best regards, Буравчик
Re[4]: Мастер-класс по ФП
От: Beam Россия  
Дата: 21.01.09 22:58
Оценка: 25 (2)
Здравствуйте, 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..n
          count <- fmap length $ zipWithM writeFile (map show [1..]) sublists
          -- Читаем (опять же лениво) содержимое всех ранее созданных файлов
          sublists <- mapM (fmap lines readFile.show) [1..count]
          -- Сливаем их и записываем результат
          putStr (unlines (mergesort' compare sublists)


Всё таки для Haskell ограничения по памяти противопоказаны
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Best regards, Буравчик
Re[5]: Мастер-класс по ФП
От: BulatZiganshin  
Дата: 21.01.09 23:14
Оценка:
Здравствуйте, Beam, Вы писали:

BZ>>на самом деле, даже если я ни в чём не ошибся — этот код будет держать все данные в памяти до победного. extra points — за изящное решение этой маааленькой проблемы


B>Я, вроде, разобрался в чем тут проблемы — все дело в length sublists. Из-за этого пропадает ленивость.

B>Если же переписать вот так, то все будет работать:

снимаю шляпу. до этого я не додумался
Люди, я люблю вас! Будьте бдительны!!!
Re[16]: Мастер-класс по ФП
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 22.01.09 14:37
Оценка:
Здравствуйте, 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);;


Пример работы:
let allfiles = [
  { name = "a"; size = 10; ext = "jpg" };
  { name = "b"; size = 20; ext = "jpg" };
  { name = "c"; size = 40; ext = "bmp" };
  { name = "d"; size = 35; ext = "jpg" };
  { name = "e"; size = 10; ext = "bmp" };
  { name = "f"; size = 60; ext = "gif" };
  { name = "g"; size = 45; ext = "gif" };
  { name = "h"; size = 20; ext = "gif" };
  { name = "i"; size = 15; ext = "jpg" };
  { name = "j"; size = 60; ext = "gif" };
  { name = "k"; size = 25; ext = "jpg" };
  { name = "l"; size = 20; ext = "gif" };
];; 
 
let show_file file =
  Printf.sprintf "(%s.%s %d)" file.name file.ext file.size;;

let show =
  List.map (List.map show_file >> String.join ", ") >> String.join "\n";;   
      
make_blocks "4f105b" allfiles |> show |> print_endline;;

(*выводит:
(a.jpg 10), (b.jpg 20), (c.bmp 40), (d.jpg 35)
(e.bmp 10), (f.gif 60)
(g.gif 45), (h.gif 20), (i.jpg 15)
(j.gif 60), (k.jpg 25), (l.gif 20)
*)

make_blocks "e" allfiles |> show |> print_endline;;

(*выводит:
(a.jpg 10), (b.jpg 20)
(c.bmp 40)
(d.jpg 35)
(e.bmp 10)
(f.gif 60), (g.gif 45), (h.gif 20)
(i.jpg 15)
(j.gif 60)
(k.jpg 25)
(l.gif 20)
*)


Использованы только иммутабельные структуры и чистые функции. Вполне возможно, что императивный вариант был бы проще.
Re[17]: Мастер-класс по ФП
От: BulatZiganshin  
Дата: 22.01.09 16:34
Оценка:
Здравствуйте, D. Mon, Вы писали:

DM>Использованы только иммутабельные структуры и чистые функции.


отсутствие побочных эффектов, конечно, хорошо, так как облегчает отладку, но на мой взгляд это ещё не максимум того, что можно выжать из фп подхода. для меня фп стиль — это поток преобразований входных данных в выходные, в идеале без всяких вспомогательных переменных

в данном случае это может быть поток преобразования от строки критериев к *функции* разбиения на подсписки. и состоять он может из таких шагов:

1) разбить строку на отдельные критерии
2) преобразовать каждый из них в функцию разбиения на [ленивые] подсписки по одному этому критерию
3) скомбинировать эти функции в итоговую

и вообще задача тренинга, как я её себе ставил — помочь перейти от мышления, ориентированного на манипуляцию данными, к мышлению, ориентированному на манипуляцию функциями

пробуйте
Люди, я люблю вас! Будьте бдительны!!!
Re[5]: Мастер-класс по ФП
От: BulatZiganshin  
Дата: 22.01.09 16:57
Оценка:
Здравствуйте, Beam, Вы писали:

B>Я, вроде, разобрался в чем тут проблемы — все дело в length sublists. Из-за этого пропадает ленивость.


кинул в cafe результат нашего творчества
Люди, я люблю вас! Будьте бдительны!!!
Re[18]: Мастер-класс по ФП
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 23.01.09 04:45
Оценка:
Здравствуйте, BulatZiganshin, Вы писали:

BZ>2) преобразовать каждый из них в функцию разбиения на [ленивые] подсписки по одному этому критерию

BZ>3) скомбинировать эти функции в итоговую

Хм, если они будут разбивать независимо друг от друга, получится плохо:
Пусть 1-я функция разбивает по количеству файлов, а вторая — по суммарному размеру. Тогда их разбиения могут выглядеть так:
1) 11112222333344445555
2) 11222233334444555567
Что будет результатом комбинирования?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.