Re[7]: Мастер-класс по ФП
От: deniok Россия  
Дата: 05.01.09 08:14
Оценка:
Здравствуйте, geniepro, Вы писали:

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


NGG>>Использование функции c не безопасно — н-р, можно при правке кода поменять a на a', а про b забыть...

NGG>>Компилятор промолчит. Может быть даже тесты пройдут успешно, т.к. разница между b и b' крылась в каком-то редком случае почему-то не покрытому при тестировании. А в итоге приложение не отработает как следует в самый ответственный момент.

G>Ну и что тут необычного? Это постоянно происходит в любых языках и парадигмах -- ФП, очевидно, не является серебрянной пулей.

G>Конкретно с этой проблемой может быть могут немного уменьшить опасность зависимые типы, когда их сделают простыми для использования. И то не на 100%...

Почему не на 100%? Если использовать технику наподобие доступной в COQ, когда одновременно генерируется программа на Хаскеле или Камле и доказательство корректности всех ее требуемых инвариантов, то таки на 100% Правда назвать использование COQ простым язык не поворачивается
Re[8]: Мастер-класс по ФП
От: geniepro http://geniepro.livejournal.com/
Дата: 05.01.09 09:31
Оценка:
Здравствуйте, deniok, Вы писали:

D>Почему не на 100%? Если использовать технику наподобие доступной в COQ, когда одновременно генерируется программа на Хаскеле или Камле и доказательство корректности всех ее требуемых инвариантов, то таки на 100% Правда назвать использование COQ простым язык не поворачивается


Так вот тут-то и проблема -- указать все инварианты просто нереально, всегда появятся новые требования, и где-то что-то будет забыто. А значит и доказательство соответствия инвариантам не будут 100%-полными...
Re[9]: Мастер-класс по ФП
От: deniok Россия  
Дата: 05.01.09 09:46
Оценка:
Здравствуйте, geniepro, Вы писали:


G>Так вот тут-то и проблема -- указать все инварианты просто нереально, всегда появятся новые требования, и где-то что-то будет забыто. А значит и доказательство соответствия инвариантам не будут 100%-полными...


Не, ну если ты забыл запрограммировать новые требования или откомпилировать их, то да. Но если тебе удалось откомпилировать программу на COQ, то результирующий хаскелловский код удовлетворяет всем инвариантам, при этом формальное доказательство этого факта лежит рядом.
Re[7]: Мастер-класс по ФП
От: Аноним  
Дата: 05.01.09 11:03
Оценка: :)
Здравствуйте, dmz, Вы писали:

dmz>Уникальные элементы таким образом не отфильтруешь.


Упражнение для первого класса...
class UniqueFilter<T> implements IFilter<T> {
    private Collection<T> uniqueData = new HashSet();
    private IFilter<T> filter;
    UniqueFilter(IFilter<T> filter) {
       this.filter = filter;
    }
    
    boolean match(T t) {
       if (uniqueData.contains(t)) {
           return false;
       }
       if (filter.match(t)){ 
           uniqueData.add(t);
           return true;
       }
       return false;
    }
}
Re[8]: Мастер-класс по ФП
От: dmz Россия  
Дата: 05.01.09 12:02
Оценка:
dmz>>Уникальные элементы таким образом не отфильтруешь.

А>Упражнение для первого класса...


Да, как-то уже варианты с побочными эффектами фильтруешь бессознательно. Минуса два — первый —
накопление элементов в фитре (по сути — предикате), в там, где мы совсем не ожидаем.

Второй — если нам потребуется операция, которая обрабатывает всю коллекцию — мы можем или дальше
хачить фильтр — т.е. в первый проход накапливаем, во второй фильтруем — или вводить новую сущность — собственно,
сам процесс фильтрации, интерфейс, класс и вставлять его в цепочку операций. Делать в фильтре — фейл дизайна,
вводить новую сущность — куча писанины для единичной элементарной операции.
Re[5]: Мастер-класс по ФП
От: BulatZiganshin  
Дата: 05.01.09 12:18
Оценка:
Здравствуйте, Tonal-, Вы писали:

BZ>>как-то всё это сложно

T>Ну я это и нарисовал. Только использовал списковые дополнения вместо map-ов

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

T>И тем более предсказать трудоёмкость.

T>Например, решение через take и drop скорее всего проигрывает по сравнению с mapMaybe (stripPrefix (make dir)). По крайней мере в С/С++ я мог бы это отразить. Но как это в haskell-е я пока не в курсе.

меньше всего интересует. в большинстве случаев неважно, в остальных это только один из аспектов оптимизации
Люди, я люблю вас! Будьте бдительны!!!
Re[7]: Мастер-класс по ФП
От: thesz Россия http://thesz.livejournal.com
Дата: 05.01.09 15:19
Оценка: :)
NGG>>Компилятор промолчит. Может быть даже тесты пройдут успешно, т.к. разница между b и b' крылась в каком-то редком случае почему-то не покрытому при тестировании. А в итоге приложение не отработает как следует в самый ответственный момент.

G>Ну и что тут необычного? Это постоянно происходит в любых языках и парадигмах -- ФП, очевидно, не является серебрянной пулей.


"Серебрянная пуля" имеет вполне определенный смысл — это уменьшение труда программиста.

А это — панацея.
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
Re[2]: Итог размышлнеий
От: NotGonnaGetUs  
Дата: 06.01.09 16:37
Оценка: 7 (2) +1
Здравствуйте, NotGonnaGetUs, Вы писали:

BZ>> есть список слов. надо записать в файл "a" все слова, начинающиеся на букву 'a', в файл "b" — слова на 'b' и т.д. вплоть до z


NGG>После чего глубоко задумался.


NGG>Прежде всего обобщил бы постановку задачи:


NGG>Дано: источник данных, критерий разбиения данных на группы

NGG>Надо: данные разбить на группы, обработать данные в группах.

NGG>Может кто-нибудь из "гуру" набросать, как будет выглядеть "обобщённое" решение 1-ой задачки в функциональном стиле?


Вернулся к обсуждению имевшему место быть какое-то время назад на этом форуме и нашёл слова thesz'a о том, что в цепочке
дизайн -> реализация -> отладка использование фя позволяет упростить две последнии стадии, а с первой могут помочь формальные спецификации.

Посмотрел на нашу битву реализаций и решил-таки (благо новый год и есть свободное время) познакомиться с одним из языков формальных спецификаций (Z) поближе, что в итоге позволило взглянуть на поднятую тему под немного другим углом.

Прежде всего, предложенное обобщение слишком "обобщенное": для того, чтобы перейти к дизайну, а затем реализации, требуется принять ряд решений (наложить ограничения) от которых итоговое решение довольно сильно зависит. Эти ограничения не имеют никакой связи с языком программирования или "стилем" проектирования принятым в этом языке.

Вернёмся к задаче в исходной постановке.

Её можно разбить на подзадачи разными способами.
Н-р:
1)
— Получить из списка строк Х список строк, в котором все слова начинаются с буквы Y (filterByFirstLetter :: [String] -> Char -> [String]).
— Записать в файл с именем X список слов Y (write :: String -> [String] -> IO())
Тогда итоговое решение выглядит так:
mapM_ (\letter -> write [letter] (filterByFirstLetter theData letter)) ['a'..'z']
2)
— Записать в файл с именем Х все слова из списка Y начинающиеся с префикса Х (writeWithPrefix :: String -> [String] -> IO())
Тогда итоговое решение выглядит так:
mapM_ (\letter -> writeWithPrefix [letter] theData) ['a'..'z']
3)
— Реализовать (или использовать существующую) инфраструктуру mailbox'ов.
— Реализовать компонент, который получая из mailbox'a слово пишет его в файл с именем Х.
— Реализовать компонент, который получая из mailbox'a слова пересылает их по первой букве в другие mailbox'ы используя маппинг (буква -> mailbox)
Тогда итоговое решение выглядит так (мифический синтаксис):
mapping = map (\letter -> (letter, makeFileMailBox [letter])) ['a' .. 'z']
dispatcher = makeDispatcherMailBox mapping
mapM_ (\word -> dispatch dispatcher word) theData

И т.п.

Т.е. на лицо очевидный факт, что одну и туже задачу можно решить разными способами.

Коль скоро это так, возникает закономерный вопрос: а какое из решений лучше?
То что короче или то что понятнее или то что создаёт больше повтроноиспользуемых артефактов или то что производительнее или то что использует больше существующих артефактов (и потому быстрее реализуется) или или или ...

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

Раз нет общепринятых формальных критериев, появляются эвристики выражаемые в той или иной форме.

Н-р, в ОО-мире — это разнообразные шаблоны (gof++) — стандартные решения с описанием их плюсов и минусов, антишаблоны — примеры часто встречающихся плохих решений ,"запахи" — характеристики кода, который потенциально (но не обзяательно) приведёт к проблемам в будущем (трудность поддрежки), всякие разные принципы — LSP, OCP, закон деметры и т.п.
(Оффтоп: эти эвристики иногда принимают за истину в поледней инстанции, убеждаются, что это не так и начинают хаять по чём зря, вместо того, чтобы разобраться когда и где они работают (Имхо, из всего зоопарка нужно знать только шаблоны grasp, т.к. они, как аксиомы в геометрии, дают базис на основе которого можно вывести практически любую известную "эвристику" и получить представление о том, когда ей стоит следовать, а когда нет)).

Т.к. необдуманное следование эвристикам ведёт к противоположному от ожидаемого результату и т.к. переделка всегда сопряжена с рисками (наплодить новых багов + потратить кучу времени без осязаемого полезного выхода) популярностью пользуются подходы:
"раз работает — не тронь" и "использовать первое пришедшее в голову решение" (или его более интеллектуальные вариант — "everything should be made as simple as possible, but no simpler."). При этом разрешение рисков откладывается на будущее, на момент когда они "стрельнут". И практика показывает, что такой подход к разработке, хоть и не без греха, но имеет право на жизнь.

Причём, насколько мне позволяет судить опыт (и обрывки информации почерпнутые, в том числе, на форумах rdsn) результативность следования "заветам простоты" при программировании с использованием ФЯ выше, чем если используется какой-либо из императивных языков. Причин вижу две:
— актвиное использование неизменяемых структур данных минимизирует появление сайд эффектов и, как следствие, приводит к решениям с меньшей связностью (значит вносить изменения и тестировать код существенно проще).
— лаконичный синтаксис + декларативный стиль описания алгоритмов позволяет писать достаточно крупные фрагменты кода не заботясь о его будщей судьбе, т.к. цена внесения в него измнений и переписывания "с нуля" становится сопоставимой.

(Как будет работать ФП при увеличении сложности системы — не знаю.
Время потраченное на дизайн, наверняка, будет сопоставимым со временем потраченным на дизайн в ОО-стиле.
Время потраченное на реализанию меньше, чем в случае "многословных" языков без вывода типов и синтаксического сахара, но не думаю, что на порядок (у "многословных" языков есть чудо-IDE в рукаве (с автоматическими рефакторингами, отладчиками, профайлерами и т.п.) и обширная база "готового кода" — библиотек и фреймворков, a у фя больше плясок с достижением желаемой производительности).
На внесение изменений — хз, зависит от того, насколько был хорош тот и другой дизайны.)

Но что-то я слегка отклонился от главной темы. Возвращаюсь.

Да, формально оценить качество решения без контекста невозможно. А субъективные оценки интересны обычно только самим субъектам

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

И тем не менее! Буду отвечать себе сам

Допустим я при реализации выбрал решение 1) т.к. решение 1 в 1 совпадает с постановкой задачи.
Потом мне говорят — надо ещё уметь по первым трём буквам строить файлы.
Я, следуя "заветам" пишу:
theData = ["abc", "def", "ijk", "axxx", "der", "preved", "%%%%%", "12345"]

type Discriminator a b = (a -> b)
type Group a b = (a, [b])
type GroupAction a b c = (Group a b -> c) 

groupByF :: Ord b => Discriminator a b -> [a] -> [Group b a]
groupByF discriminator list = 
    list >>== sortByF discriminator >>== groupByF discriminator >>== map (\list -> (discriminator $ head list, list))

filterByD :: (a -> Bool) -> [Group a b] -> [Group a b]
filterByD f = filter (\(groupId,_) -> f groupId)

writeConsole :: GroupAction String String (IO())                                              
writeConsole (groupId, []) = return ()
writeConsole (groupId, listOfWords) = putStrLn $ groupId ++ " -> " ++ (show listOfWords)

(>>==) :: a -> (a -> b) -> b
(>>==) x f = f x   

sortByF f = sortBy (\x y -> (f x) `compare` (f y))  
groupByF f = groupBy (\x y -> (f x) == (f y))  

main = theData 
       >>== groupByF (take 3) 
       >>== filterByD ((`elem` [x| x <- ['a'..'z']]).head)
       >>== mapM_ writeConsole

Ну, переписал всё и ладно.
А тут просят: а теперь решено формировать файлы не по префиксу, а по "содержит".
Опа... опять не получается простым движением руки изменение внести, т.к. функция дикриминатор превратилась в список дикриминаторов, причём с другой сигнатурой.

Опять переписывать groupByF?

А если в следующий раз попросят от 'a' до 'n' по префиксу писать, а от 'o' до 'z' по "содержит"?
Ешкин кот — ни старый groupByPrefix, ни новый groupByContains уже не используешь — опять писать что-то нужно.

Ну напишем:

theData = ["abc", "def", "ijk", "axxx", "der", "preved", "%%%%%", "12345", "zoo"]

type Group = (String, [String])
type GroupAction a = (Group -> a) 

data Discriminator = DByPrefix String | DByContains String | DByPostfix String

match text (DByPrefix prefix) = isPrefixOf prefix text 
match text (DByContains prefix) = isInfixOf prefix text 
match text (DByPostfix prefix) = isSuffixOf prefix text 

groupId (DByPrefix prefix) = prefix 
groupId (DByContains prefix) = prefix 
groupId (DByPostfix prefix) = prefix 

groupByD :: [Discriminator] -> [String] -> [Group]
groupByD ds ws = (groupByD' ds ws) 
                 >>== sortByF fst >>== groupByF fst 
                 >>== map (\x -> (fst $ head x, map snd x)) 
    where 
        groupByD' ds [] = []  
        groupByD' ds (w:ws) = case find (match w) ds of
                                    Just x ->  (groupId x, w) : (groupByD' ds ws)
                                    Nothing ->groupByD' ds ws   
                
filterByD :: (String -> Bool) -> [Group] -> [Group]
filterByD f = filter (\(groupId,_) -> f groupId)

writeConsole :: GroupAction (IO())                                              
writeConsole (groupId, []) = return ()
writeConsole (groupId, listOfWords) = putStrLn $ groupId ++ " -> " ++ (show listOfWords)

(>>==) :: a -> (a -> b) -> b
(>>==) x f = f x   

sortByF f = sortBy (\x y -> (f x) `compare` (f y))  
groupByF f = groupBy (\x y -> (f x) == (f y))  

main = theData 
       >>== groupByD ((map DByPrefix [[x]| x <- ['a'..'n']]) ++ (map DByContains [[x]| x <- ['o'..'z']])) 
       >>== filterByD ((`elem` [x| x <- ['a'..'z']]).head) 
       >>== mapM_ writeConsole


А тут говорят, хочу слова начинающиеся в префикса 'a' писать в консоль, а содрежащие 'a' или начинающиеся с 'b' в файл и имя файла сам хочу выбрать...

Скрепя сердцем делаю:
theData = ["abc", "def", "ijk", "axxx", "der", "preved", "%%%%%", "12345", "zoo"]

--- IFilter and implementations?
data Discriminator = DByPrefix String | DByContains String | DByPostfix String

matchD text (DByPrefix prefix) = isPrefixOf prefix text 
matchD text (DByContains prefix) = isInfixOf prefix text 
matchD text (DByPostfix prefix) = isSuffixOf prefix text 

discrId (DByPrefix prefix) = prefix 
discrId (DByContains prefix) = prefix 
discrId (DByPostfix prefix) = prefix 

--- IProcessor and implementations ?
type GroupAction = (Group -> IO()) 

makeConsoleAction1 :: String -> GroupAction 
makeConsoleAction1 _ (_, []) = return ()
makeConsoleAction1 name (_, listOfWords) =  putStrLn $ name ++ " (prefix)-> " ++ (show listOfWords)

makeConsoleAction2 :: String -> GroupAction 
makeConsoleAction2 _ (_, []) = return ()
makeConsoleAction2 name (_, listOfWords) =  putStrLn $ name ++ " (contains)-> " ++ (show listOfWords)
  
writeConsole :: GroupAction                                              
writeConsole (_, []) = return ()
writeConsole (groupInfo, listOfWords) = putStrLn $ (groupId groupInfo) ++ " -> " ++ (show listOfWords)


--- IGroup and implementations ?
data GroupInfo = FilteredGroup GroupAction Discriminator

discriminator (FilteredGroup _ d) = d
action (FilteredGroup a _) = a
    
match text groupInfo = matchD text $ discriminator groupInfo 
groupId groupInfo = discrId $ discriminator groupInfo 

--- mutable (in oo) state of IGroup or IProcessor ... 
type Group = (GroupInfo, [String])

processGroup (groupInfo, listOfWords) = (action groupInfo) (groupInfo, listOfWords)

--- group builder...
groupByD :: [GroupInfo] -> [String] -> [Group]
groupByD ds ws = (groupByD' ds ws) 
                 >>== sortByF (groupId.fst) >>== groupByF (groupId.fst) 
                 >>== map (\x -> (fst $ head x, map snd x)) 
    where 
        groupByD' ds [] = []  
        groupByD' ds (w:ws) = case find (match w) ds of
                                    Just info -> (info, w) : (groupByD' ds ws)
                                    Nothing -> groupByD' ds ws   
                
filterByD :: (String -> Bool) -> [Group] -> [Group]
filterByD f = filter (\(groupInfo,_) -> f $ groupId groupInfo)

(>>==) :: a -> (a -> b) -> b
(>>==) x f = f x   

sortByF f = sortBy (\x y -> (f x) `compare` (f y))  
groupByF f = groupBy (\x y -> (f x) == (f y))  

--- solution itself
main = theData 
       >>== groupByD groupInfos  
       >>== filterByD ((`elem` [x| x <- ['a'..'z']]).head)  
       >>== mapM_ processGroup
    where        
       groupInfos = (map (\d -> FilteredGroup (makeConsoleAction1 (discrId d)) d) (map DByPrefix [[x]| x <- ['a'..'n']]))
                   ++ (map (\d -> FilteredGroup (makeConsoleAction2 (discrId d)) d) (map DByContains [[x]| x <- ['g'..'z']]))

... и видим как близко решение оказывается к глупому оо.

А когда замечаем, что между делом потерялся способ сгруппировать по первым нескольким буквам ... становится ясно, что ещё копать и копать до финала. Слёта даже не могу придумать как это более или менее красиво реализовать четвёртый тип дискриминатора (DByLength Int)

За сим откланиваюсь, будет критика пишите.

з.ы.
К дискуссии о "промышленном" решении можно будет вернуться, когда ФП окажется в массах и массы, пробежавшись по всем граблям n-раз, обобщат их до шаблонов, "запахов" и приниципов

з.з.ы.
Тому кто осилил — респект.
Re: Мастер-класс по ФП
От: subdmitry Россия  
Дата: 06.01.09 22:25
Оценка: :))
Здравствуйте, BulatZiganshin, Вы писали:

BZ>я часто сталкиваюсь с двумя точками зрения — первая "я знаком с ФП, но оно никакой выгоды в программировании не даёт", и вторая "я знаком с ФП, но так и не научился использовать его для повышения эффективности программирования". собственно, это одно и то же, разница только в том, на кого (ФП или самого человека) возлагается вина за отсутствие результата


Еще одно мнение в копилку — ФП дает выигрыш на узком классе задач, связанных со сложной работой с динамическими структурами данных (списками, деревьями, etc) — на что оно, собственно, и заточено. А на том, что типично пишут программисты, оно особенно не нужно.

Кстати, давно хочу полюбоваться, как на Хаскелле запишется следующий императивный код:
void DoIt() {
  A++;
  B[C].D*=10;
  F(G).H=0;
}

Хаскеллисты что-то отказываются решать такую задачу.

Серьезно хочу, не только чтобы какие-то там языки унизить — свои-то минусы можно найти у любого языка.
And if you listen very hard the alg will come to you at last.
Re[2]: Мастер-класс по ФП
От: deniok Россия  
Дата: 06.01.09 22:33
Оценка: +1 :)
Здравствуйте, subdmitry, Вы писали:


S>Кстати, давно хочу полюбоваться, как на Хаскелле запишется следующий императивный код:

S>
S>void DoIt() {
S>  A++;
S>  B[C].D*=10;
S>  F(G).H=0;
S>}
S>

S>Хаскеллисты что-то отказываются решать такую задачу.

Это не задача. Это выглядящие бессмысленными упражнения с записыванием значений в непонятные участки памяти, очевидно, доступные глобально.
Re[3]: Мастер-класс по ФП
От: subdmitry Россия  
Дата: 06.01.09 22:40
Оценка:
Здравствуйте, deniok, Вы писали:

D>Это не задача. Это выглядящие бессмысленными упражнения с записыванием значений в непонятные участки памяти, очевидно, доступные глобально.


Я считаю, что начинать надо с малого. Лично мне не очень понятно, как добиться на Хаскелле решения такой простой проблемы и (серьезно!) хочу его увидеть.

Оно не такое уж и бессмысленное. У меня при программировании часто возникает код, подобный написанному. Хочу понять, как получить его аналог в функциональных языках.

Некоторые тут, кстати, заявляют, что Хаскель лучший императивный язык...
And if you listen very hard the alg will come to you at last.
Re[4]: Мастер-класс по ФП
От: deniok Россия  
Дата: 06.01.09 23:09
Оценка: +3
Здравствуйте, subdmitry, Вы писали:

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


D>>Это не задача. Это выглядящие бессмысленными упражнения с записыванием значений в непонятные участки памяти, очевидно, доступные глобально.


S>Я считаю, что начинать надо с малого. Лично мне не очень понятно, как добиться на Хаскелле решения такой простой проблемы и (серьезно!) хочу его увидеть.


S>Оно не такое уж и бессмысленное. У меня при программировании часто возникает код, подобный написанному. Хочу понять, как получить его аналог в функциональных языках.


Если у тебя возникает такой код, то это очень плохо даже в императивных языках. У тебя там семь идентификаторов, смысл которых неизвестен в рамках данной функции. Такая связность по данным DoIt с внешним миром бессмысленна. Даже в обычном процедурном программировании считается предпочтительным, чтобы внешний мир общаелся с функцией через ее аргументы, а не через глобальные данные.
Re[5]: Мастер-класс по ФП
От: subdmitry Россия  
Дата: 06.01.09 23:36
Оценка: -1 :))
Здравствуйте, deniok, Вы писали:

S>>Оно не такое уж и бессмысленное. У меня при программировании часто возникает код, подобный написанному. Хочу понять, как получить его аналог в функциональных языках.


D>Если у тебя возникает такой код, то это очень плохо даже в императивных языках. У тебя там семь идентификаторов, смысл которых неизвестен в рамках данной функции. Такая связность по данным DoIt с внешним миром бессмысленна.


Да нормальная связанность. Предположим, что у нас есть функция на 100 строк, три из которых что-то меняют в глобальном состоянии программы. Предположим, что я привел только эти три строчки и хочу узнать, как их реализовать.

Если тебе нужен пример из реальной жизни, представь себе download manager (качалку файлов), который при получении очередной порции данных по сети изменяет счетчик всех скачаных байт за сеанс, изменяет количество скачанных байт у данного файла и изменяет количество скачанных байт у данного типа (группы) файлов, который определяется для данного файла посредством вызова функии. Вот тебе и три воздействия на глобальные переменные в одной функции (уж не будем зацикливаться на деталях, что там за операции именно используются).

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

D>Даже в обычном процедурном программировании считается предпочтительным, чтобы внешний мир общаелся с функцией через ее аргументы, а не через глобальные данные.


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

Короче надо понимать ситацию так, что реализация такой вещи на Хаскелле настолько страшна, что ее никто не приведет.
And if you listen very hard the alg will come to you at last.
Re[3]: Итог размышлнеий
От: Beam Россия  
Дата: 07.01.09 01:21
Оценка: 19 (2)
Здравствуйте, NotGonnaGetUs, Вы писали:

BZ>>> есть список слов. надо записать в файл "a" все слова, начинающиеся на букву 'a', в файл "b" — слова на 'b' и т.д. вплоть до z

NGG>>Прежде всего обобщил бы постановку задачи:
NGG>>Дано: источник данных, критерий разбиения данных на группы
NGG>>Надо: данные разбить на группы, обработать данные в группах.
NGG>> как будет выглядеть "обобщённое" решение 1-ой задачки в функциональном стиле?

import Data.List
import Data.Ord

-- РЕШЕНИЕ

-- универсальная функция, группирует список пар (ключ, значение) по ключу, возвращая список пар (ключ, [значения для этого ключа])
-- например groupByFst [(1,2), (1,3), (2,5), (1,4), (2,6)] возвращает [ (1,[2,3,4]), (2,[5,6]) ]
groupByFst ps = [ (fst $ head x, map snd x) | x <- groupBy (\a b -> fst a == fst b) $ sortBy (comparing fst) ps]

-- группируем слова по месту назначения на основе fun (слово -> список куда записать), получаем список пар (место назначения, слова)
-- затем вызываем обработчик process (куда записать -> слова -> IO) для каждой такой пары
processWords fun process words = mapM_ write $ groupedWords
  where
    write (target, words) = process target words
    groupedWords = groupByFst $ [ (target, word) | word <- words, target <- fun word]

-- ИСПОЛЬЗОВАНИЕ

data Target = Console | File String | TempFile deriving (Eq, Ord, Show)

process :: Target -> [String] -> IO ()

process (File filename) words = do
  putStrLn $ "To file " ++ filename ++ " : "
  mapM_ putStrLn words

process Console words = do
  putStrLn $ "To console: "
  mapM_ putStrLn words

process TempFile words = do
  putStrLn $ "To temp file: "
  mapM_ putStrLn words

-- записываем слово в файл в соответствие с первой буквой слова
targets word = [File $ take 1 word ++ ".txt", Console] 

-- записываем слово в файл в соответствие с тремя первыми буквами слова
targets2 word = [File $ take 3 word ++ ".txt", Console] 

-- записываем слово в файл "?.txt" если слово содержит букву ?
-- дополнительно слова, длиной 3 пишем во временный файл
-- дополнительно слова, начинающиеся с гласной буквы пишем в консоль
targets3 word = byContain word ++ byLen3 word ++ byVowel word
  where
    byContain letters = [File $ s:".txt" | s <- letters]
    byLen3 letters | length letters == 3 = [TempFile]
                   | otherwise = []
    byVowel letters | head letters `elem` ['e','u','i','o','a'] = [Console]
                    | otherwise = []

-- MAIN

sourceData = ["test1", "test2", "aaa", "aaz", "aabb", "happy", "new", "year"]

main = do
  processWords targets3 process sourceData


Непосредственно обработкой занимается processWords (универсальное решение)
Обработку мы настраиваем двумя функциями (передаются в качестве параметра processWord):
1. targets — по имени файла говорит, куда этот файла надо записать (в консоль, в конкретный файл, никуда)
2. process — выполняет саму обработку группы слов
На самом деле, то же можно повторить и в ООП Разный подход будет в "настройке".
Думаю решение на ООП будет выглядеть чуть по сложнее, т.к., на мой взгляд, функцию легче охватить взглядом и понять, чем класс ООП.
Не говоря уже о добавлении нескольких классов / интерфейсов.

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


В любом случае "промышленный" код будет потяжелее.
Скормил программке файл на 130 мегов, подавилась, пришлось переписать без сортировки, с чтением файлов по-строчно, хранением хэндлов и т.п.

А из отличий ФП и ООП я бы выделил то, что в ФП легче искать и выделять абстракции. А также легче читать (и писать) программу, т.к. работаешь с меньшим количество кода. И не потому, что ФП лаконичнее, а скорее потому, что независимые куски кода получаются значительно короче.
Best regards, Буравчик
Re[2]: Мастер-класс по ФП
От: yumi  
Дата: 07.01.09 03:30
Оценка:
Здравствуйте, subdmitry, Вы писали:

S>Кстати, давно хочу полюбоваться, как на Хаскелле запишется следующий императивный код:

S>
S>void DoIt() {
S>  A++;
S>  B[C].D*=10;
S>  F(G).H=0;
S>}
S>

S>Хаскеллисты что-то отказываются решать такую задачу.

Оно и понятно, за такое тебе и грамотные императивщики по башке надают.

S>Серьезно хочу, не только чтобы какие-то там языки унизить — свои-то минусы можно найти у любого языка.


Пойми я тоже не хочу никого унизить, просто тут уже сразу видно невооруженным взглядом, отсутствие какой-либо базы, а без базы, тебе никто не сможет даже объяснить, почему ты не прав.
Lisp is not dead. It’s just the URL that has changed:
http://clojure.org
Re[4]: Мастер-класс по ФП
От: VoidEx  
Дата: 07.01.09 03:52
Оценка:
Здравствуйте, subdmitry, Вы писали:

S>Я считаю, что начинать надо с малого. Лично мне не очень понятно, как добиться на Хаскелле решения такой простой проблемы и (серьезно!) хочу его увидеть.


Я бы советовал начинать сразу с throw или longjmp

S>Оно не такое уж и бессмысленное. У меня при программировании часто возникает код, подобный написанному. Хочу понять, как получить его аналог в функциональных языках.


Это плохо, что возникает код, который лезет в какие-то глобальные данные
Re[3]: Мастер-класс по ФП
От: subdmitry Россия  
Дата: 07.01.09 04:06
Оценка:
Здравствуйте, yumi, Вы писали:

Y>Пойми я тоже не хочу никого унизить, просто тут уже сразу видно невооруженным взглядом, отсутствие какой-либо базы, а без базы, тебе никто не сможет даже объяснить, почему ты не прав.


А при чем тут вообще прав/неправ? Был приведен пример реальной задачи (download manager), в которой возникает код, аналогичный привденному. Я не спрашиваю, прав я или неправ, я предлагаю привести решение этой задачи на Хаскелле. Особенно интересно в многопоточном варианте.

Если, конечно, наличествующая у вас база это позволяет сделать, а не просто утверждать, что "глобальные переменные это плохо".
And if you listen very hard the alg will come to you at last.
Re[5]: Мастер-класс по ФП
От: subdmitry Россия  
Дата: 07.01.09 04:18
Оценка:
Здравствуйте, VoidEx, Вы писали:

VE>Это плохо, что возникает код, который лезет в какие-то глобальные данные


О! Вот и продемонстрируйте, как можно на функциональном языке прекрасно без этого обойтись. Пример в студии.
And if you listen very hard the alg will come to you at last.
Re[6]: Мастер-класс по ФП
От: VoidEx  
Дата: 07.01.09 04:28
Оценка:
Здравствуйте, subdmitry, Вы писали:

S>Если тебе нужен пример из реальной жизни, представь себе download manager (качалку файлов), который при получении очередной порции данных по сети изменяет счетчик всех скачаных байт за сеанс, изменяет количество скачанных байт у данного файла и изменяет количество скачанных байт у данного типа (группы) файлов, который определяется для данного файла посредством вызова функии. Вот тебе и три воздействия на глобальные переменные в одной функции (уж не будем зацикливаться на деталях, что там за операции именно используются).

Зачем этим должна заниматься функция получения данных? А если фильтр изменится (на группы файлов)? Это глупо само по себе. А так, функция принимает данные, возвращает кол-во прочитанных байт, которые прибавляются к текущим прочитанным вообще, для данного файла и для группы. Можешь их потом на экран вывести, если хочешь.

S>Для полного прикола можно еще представить, что такие функции вызываются в каждом независимо работающем треде, обслуживающем скачку одного из нескольких одновременно качающихся файлов, так что передавать состояние системы в функцию и получать его назад измененным не получится.


Вот вам полный прикол:
-- Сюда будем писать, сколько байт прочитано
type ByteReads = Chan (Int -> Int)

-- Кушает пользовательский ввод. На 1 - пишет "прочитано 10 байт", на 0 - выход
writer :: ByteReads -> IO ()
writer chan = loop
    where
        loop = getChar >>= command
        command '0' = printf "done\n"
        command '1' = writeChan chan (+10) >> loop
        command '\n' = loop -- ignore
        command c = printf "Illegal: %c\n" c >> loop

-- 1000 раз непрерывно пишет "прочитано cnt байт"
randomWriter chan cnt = do
    mapM_ (writeChan chan) $ take 1000 $ repeat (+cnt)

-- Выводит статистику, сколько байт прочитано
reader :: Int -> [Int -> Int] -> IO ()
reader s xs = mapM_ (printf "Received %d bytes\n") $ scanl (\v op -> op v) s xs

main :: IO ()
main = do
    chan <- newChan :: IO ByteReads
    s <- getChanContents chan
    w1 <- forkIO $ reader 0 s -- Читатель
    w2 <- forkIO $ randomWriter chan 1 -- Пишет 1000 раз по 1 байту
    w3 <- forkIO $ randomWriter chan 2 -- по 2
    w4 <- forkIO $ randomWriter chan 5 -- по 5
    writer chan -- Команды от пользователя


Кстати, я думал, что в Chan может быть один писатель, но не падает. Везёт, или писателей таки может быть куча?

S>Короче надо понимать ситацию так, что реализация такой вещи на Хаскелле настолько страшна, что ее никто не приведет.

Да нет, вон она как проста, когда надо не изменить 5 глобальных переменных в массивах, а решить саму задачу.
Re[6]: Мастер-класс по ФП
От: VoidEx  
Дата: 07.01.09 04:29
Оценка:
Здравствуйте, subdmitry, Вы писали:

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


VE>>Это плохо, что возникает код, который лезет в какие-то глобальные данные


S>О! Вот и продемонстрируйте, как можно на функциональном языке прекрасно без этого обойтись. Пример в студии.


Написал, пожалста
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.