D>или (для любителей явных операторов) приводим к тому, что просили D>
D>(***) = flip
D>map *** [1,2,3]
D>
Оригинально.
B>>2. существует функции у которых количество параметров больше двух D>А тут отложенное неименованное связывание легко рушит семантику
B>>3. можно было бы работать с tuple: fun (_,1) _ ===> \x y -> fun (x,1) y D>Да-да, вот тут и рушит Представь, что fun это flip и вспомни что вычисления идут лениво слева направо...
Вот здесь не понял. Ведь это ничем не отличается от
let myFun x y = fun (x,1) y in myFun 1 2
И вроде, здесь нет проблем никаких
T>>>>vA = ...что-то там такое... S>Ага, так вот они, глобальные переменные в Хаскелле! Все, понял. Действительно, императивные фичи можно реализовать, что радует.
Ну, это уж ты как-то совсем того.
Есть такой сайт, Lambda the Ultimate, назван по серии статей Гая Стила (Guy Steele), одного из разработчиков Java.
В этой серии одна из статей называется "Lambda the Ultimate Imperative". AFAIK, там рассказывается про императивное программирование с точки зрения ЛИ.
Могу ошибаться, правда.
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
B>>>3. можно было бы работать с tuple: fun (_,1) _ ===> \x y -> fun (x,1) y D>>Да-да, вот тут и рушит Представь, что fun это flip и вспомни что вычисления идут лениво слева направо...
B>Вот здесь не понял. Ведь это ничем не отличается от B>let myFun x y = fun (x,1) y in myFun 1 2 B>И вроде, здесь нет проблем никаких
У тебя два неименованных места для будущего связывания
myComb = fun (_,1) _
Если потом вызвать
myComb x y
то связывание просто по месту не пройдет, нужны имена (обычная лямбда) или номера (индексы де Бр(ой)(ау)на). Поскольку если fun у тебя, например, такой
fun (a,b) c = c a
то вычисление пойдет так
myComb x y ~>
(fun (_,1) _) x y ~>
(_ _) x y ~>
x y
-- ой мама биндеры переставились, должно-то быть y x
Здравствуйте, thesz, Вы писали:
T>Сделай-ка аналогичный "ассемблер" на твоём любимом ЯП. Чтобы не менее удобно было — чтобы можно было определять свои "подпрограммы" и тп.
Здравствуйте, deniok, Вы писали:
D>Здравствуйте, Beam, Вы писали:
B>>Кстати, почему в Haskell не сделают что-то типа B>>map _ [1,2,3] as \f -> map f [1,2,3] B>>Ну т.е. аналогично scheme.
D>А зачем, если можно с той же целью написать D>
Здравствуйте, Beam, Вы писали:
B>Кстати, почему в Haskell не сделают что-то типа B>map _ [1,2,3] as \f -> map f [1,2,3]
я слышал, что проблема в том, что непонятно, где вставлять \_. например в твоём примере:
map (\f -> f) [1,2,3]
или
\f -> map f [1,2,3]
или
(\f -> map f) [1,2,3]
Здравствуйте, thesz, Вы писали:
T>>>То есть, он не свопится, когда у нас есть 300 тысяч потоков? (каждый по (327+233)*word_size, то есть, 1.2К)
К>>Откуда ты плюс взял?
T>Просмотрел. Но я нашёл поинтересней.
Ммм, Сергей, думаю стоит внимательней читать, ну какое отношение стэк эмулятора имеет к памяти тех процессов, которые в нём крутятся?
И чуть ниже смотрим:
+h Size
Sets the default heap size of processes to the size Size.
Вот этот другой (а не +a size) параметр задаёт размер хипов процессов.
Здравствуйте, Tonal-, Вы писали:
T>1. Зачитать это в T>
T>data Tree = Leaf Int String | Tree Int String [Tree]
T>
T>где Int — номер строки, String — строка без начатльных и конечных пробелов
во-первых, проще
data Tree = Tree Int String [Tree]
во-вторых, в постановке задачи ошибка попробуй сам описать, какое дерево должно быть построено по твоим входным данным
task1 = zip [1..] -- нумеруем строки
>>> makeTrees -- формируем список деревьев
makeTrees = groupFrom (not.isSpace.head.snd) -- разбиваем на группы, начинающиеся со строки без отступа
-- в каждой из групп первую строчку используем для заголовка дерева
>>> map ( \((linenum,string):xs) -> Tree linenum string
-- а в оставшихся строках отрезаем первые два пробела и
-- рекурсивно изготовляем из них список деревьев
(makeTrees (map (drop 2) xs)))
T>2. Сделать чтение устойчивым к ошибкам пропуска уровня типа: T>
Действительно "мастер-класс"!
У меня кода получилось в несколько раз больше.
BZ>не совсем понятно, что значит сделать устойчивым? на мой взгляд, нужно просто подавать жалобу из groupFrom: BZ>
BZ>groupFrom crit [] = []
BZ>groupFrom crit (x:xs) | not (crit x) = error"ах вы суки эдакие!!!"
BZ>groupFrom crit (x:xs) = let (l1:l2) = break crit xs
BZ> in (x:l1) : groupFrom crit l2
BZ>
Я имел в виду, что дерево нужно строить дальше, хотя кончно так и нужно было написать в постановке.
Хотя groupFrom довольно просто изменить под это:
groupFrom crit [] = []
groupFrom crit (x:xs) =
let
(l1:l2) = break crit xs
grp x l | not (crit x) = (fst x, "(!)") : x : l
grp x l = x:l
in grp x l1 : groupFrom crit l2
T>>The default suggested stack size is 16 kilowords T>>Оба-на! К>Ммм, Сергей, думаю стоит внимательней читать, ну какое отношение стэк эмулятора имеет к памяти тех процессов, которые в нём крутятся?
А стек самого процесса, который крутится, где задаётся?
Как я понимаю, он у каждого "легкого процесса" должен быть свой.
К>И чуть ниже смотрим: К>
К>+h Size
К> Sets the default heap size of processes to the size Size.
К>Вот этот другой (а не +a size) параметр задаёт размер хипов процессов.
Я про стек начал говорить. Стек для каждого из процессов, наверное, в хип не входит.
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
Здравствуйте, thesz, Вы писали:
T>>>The default suggested stack size is 16 kilowords T>>>Оба-на! К>>Ммм, Сергей, думаю стоит внимательней читать, ну какое отношение стэк эмулятора имеет к памяти тех процессов, которые в нём крутятся?
T>А стек самого процесса, который крутится, где задаётся?
T>Как я понимаю, он у каждого "легкого процесса" должен быть свой.
А фиг знет где конкретно, тупо просмотром через запуск процесса и вызов process_info/1 выдаёт 8 слов. Но сдаётся мне, что они как раз в те предыдущие 327 как раз входят.
А как можно изменить этот код чтобы в в начале каждой строки отображался linenum нода?
T>>4. Отфильтровать дерево, оставив только ветви содержащие дубликаты и сами дубликаты.
И опять я не точно выразился.
Дубликатами считаются одинаковые строки одного родителя.
Например:
Животные
рыбы
щука
карась
щука
После фильтрации должно получится:
Животные
рыбы
щука
щука
Как подправить, сходу не соображу.
Кстати, в моей инсталяции ghc 6.10.1 для винды, в документации нет упоминания mapCatMaybes и описания модуля Maybes тоже нет. Зато есть пустая страница для модуля Maybe
T>>А стек самого процесса, который крутится, где задаётся? T>>Как я понимаю, он у каждого "легкого процесса" должен быть свой. К>А фиг знет где конкретно, тупо просмотром через запуск процесса и вызов process_info/1 выдаёт 8 слов. Но сдаётся мне, что они как раз в те предыдущие 327 как раз входят.
Короче говоря, ничего не понятно.
Ты бы провёл эксперимент с 300K отправителей.
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
Здравствуйте, Tonal-, Вы писали:
T>Кстати, в моей инсталяции ghc 6.10.1 для винды, в документации нет упоминания mapCatMaybes и описания модуля Maybes тоже нет. Зато есть пустая страница для модуля Maybe
По-моему функции mapCatMaybes в стандартной библиотеке нет сейчас и не было раньше.
А Maybe перенесли в Data.Maybe. Но модуль Maybe тоже будет работать, так как просто импортирует Data.Maybe
Здравствуйте, Tonal-, Вы писали:
T>А как можно изменить этот код чтобы в в начале каждой строки отображался linenum нода?
show?
T>>>4. Отфильтровать дерево, оставив только ветви содержащие дубликаты и сами дубликаты. T>Как подправить, сходу не соображу.
строй список дубликатов локально
T>Кстати, в моей инсталяции ghc 6.10.1 для винды, в документации нет упоминания mapCatMaybes и описания модуля Maybes тоже нет. Зато есть пустая страница для модуля Maybe
это из модуля Data.Maybe, я просто держу на диске исходники
B>-- универсальная функция, группирует список пар (ключ, значение) по ключу, возвращая список пар (ключ, [значения для этого ключа])
B>-- например groupByFst [(1,2), (1,3), (2,5), (1,4), (2,6)] возвращает [ (1,[2,3,4]), (2,[5,6]) ]
B>-- записываем слово в файл "?.txt" если слово содержит букву ?
B>-- дополнительно слова, длиной 3 пишем во временный файл
B>-- дополнительно слова, начинающиеся с гласной буквы пишем в консоль
NGGU> type Targets = String -> [Target]
NGGU> targets3 :: Targets
B>targets3 word = byContain word ++ byLen3 word ++ byVowel word
B> where
B> byContain letters = [File $ s:".txt" | s <- letters]
B> byLen3 letters | length letters == 3 = [TempFile]
B> | otherwise = []
B> byVowel letters | head letters `elem` ['e','u','i','o','a'] = [Console]
B> | otherwise = []
B>
B>Обработку мы настраиваем двумя функциями (передаются в качестве параметра processWord): B>1. targets — по слову говорит, куда это слово надо записать (в консоль, в конкретный файл, никуда) NGGU> (Кстати, так задумано, что слово с 3 буквами 'e' попадает в файл с именем 'e' трижды?) B>2. process — выполняет саму обработку группы слов
Во всех предложенных решениях, кроме, разве что совсем не кастомизируемых, выбор списка для представления элементов группы (а именно его иммутабельность) приводит к необходимости создания конструкции вида [(ID, String)], где ID — идентификатор группы (от вырожденных случаев где ID — имя файла, до сложных — где ID содержит информацию о способе обработки), и необходимости эту конструкцию преобразовывать к виду — [(ID, [String])].
Это иллюстрация того, как выбор "архитектурного решения" (дизайн, если хотите) оказывается взависимости от выбора языка реализации, хотя, казалось бы, такого быть не должно :)
Есть, так же, довольно любопытное мнение, озвучиваемое в л-ре посвящённой ооп, согласно которому, функциональная декомпозиция имеет существенный недостаток перед оо-декопозицей. Утверждается, что при ф-декомпозиции разбиение на подзадачи зависит от выбора "главной функции" системы (в нашей задаче, честно говоря, выбор этой функции очевиден :)) и нет никакой гарантии, что при реализации новой функции получится использовать подзадачи полученные входе первоначального решения -> либо часть функциональности будет повторена, либо потребуется провести декомпозицию всего решения заново, чтобы выделить общие подзадачи...
С этим утверждением можно согласиться, когда речь идёт о "классических" процедурных языках, но в случае с современными функциональными языками, где есть фвп и прочии прелести, можно и поспорить. Чтобы опровергнуть его, нужно привести какие-то доводы в пользу того, что при декомпозиции будут полученны подзадачи имеющие самостоятельное значение для предметной области над которой определена исходная задача (а не для вычислителя). В случае ОО-декомпозиции, этого пытаются достич моделируя сущности/концепции (называйте как хотите) из предметной области в виде объектов и классов объектов, а отнощения между ними — в виде методов классов, статических методов или "синтетических" объектов. В итоге образуется набор "кубиков" из которых затем строится решение конкретной задачи. Поскольку изменения в предметной области случаются реже, чем формулируются новые задачи, вероятность использовать при решении новой задачи существующие "кубики" достаточна велика, что, в каком-то виде, гарантирует устойчивость решения к изменениям в требованиях и упрощение реализации новых требований через повторное использование не только результатов анализа предметной области, но и имеющегося кода. Минусы тут тоже есть:
— во-первых, выделение сущностей и отношений между ними процесс "творческий" и если нет достаточного опыта или таланта, то результат может оказаться плачевным.
— во-вторых, не всегда "изменения в предметной области случаются реже, чем формулируются новые задачи" -> для таких областей нужен другой подход к проектированию.
— в-третьих, существует граница, когда написать решение с нуля проще, чем разбираться во всех имеющихся "кубиках" (это относится не только к ООП, но и к ФП :))
— в-четвёртых, с определённого уровня вложенности подзадачи перестают относиться к предметной области и превращаются в "борьбу" с техническими сложностями/особенностями языка разработки и вспомогательными библиотеками (стандартными/сторонними).
Тем не менее, худо-бедно с этими минусами справиться удаётся.
В случае с ФП всё не так прозрачно. Есть требуется решить "конкретную" задачу, то всё понятно. Задачу крошим на подзадачи и красиво расписываем. Если нужно создать основу для решения родственных задач — тоже понятно — формулируется "конкретная" задача, где описываются вариации, далее задача решается "обычным" путём. Всё хорошо, но только "точки вариации" должны быть описаны _заранее_ (в отличии ОО подхода, когда "все возможные" вариации задач закладываются в модели предметной области (в классах и методах) без попытки перечислить их _все_ явно).
Булат озвучил подход при котором ФЯ используется как средство для описания dsl, на котором затем решается задача. Этот подход отличается от разбиения задачи на подзадачи и может в теории (так же как и ООП — _в теории_ :)) дать гарантии "устойчивости" о которых выше шла речь, т.к. язык предметной области должен, по сути, отражать теже сущности и отношения между ними за которыми гоняется ООП, только в виде отличном от классов и методов (и может быть даже более красивым способом) и убирать на задний план "борьбу" языком и библиотеками. Правда, в этом случае возникает вопрос "как придумать хороший dsl"... но отложим его в сторону до лучших времен
А подумалось вот о чём: может если использовать идею о dsl, то "дизайн" несчастной задачи про слова не зависил бы от языка, как указывалось выше? Надо бы попробовать :)
Итак, нужно выбрать способ формального описания условия задачи, а затем реализовать преобразование формального описания в описание на яп.
Отталкиваться будем от того, что критерий разбивки слов на группы часто меняется, поэтому dsl должен позволять быстро реализовывать новые варианты. Можно сделать так:
Очевидно, что требуются примитивы для упрощения ввода "программы":
StartWith x -> WriteToFile x, x in ["a" .. "z"]
Сontains "c" | Сontains "d" -> WriteToFile "Contains 'c | d'"
Match (wordPrefix 3) | "paa" -> WriteToConsole "debug", WriteToFile "paa"
| prefix -> WriteToFile prefix
Почесав репу, можно добавить нотацию для конфигурирования процесса группировки:
StartWith x -> WriteToFile x {unique: true}, x in ["a" .. "z"]
-- ^^ в файл пишутся только уникальные строки (дубликаты игнорируются и в другие группы не попадают)
Сontains "c" | Сontains "d" {unique: true} -> WriteToFile "Contains 'c | d'"
-- ^^ матчатся только уникальные строки (дубликаты не матчатся и могут попасть в другие группы)
Match (wordPrefix 3) | "paa" -> WriteToConsole "debug", WriteToFile "paa"
| prefix -> WriteToFile prefix
Думаю, что такой-же синтаксис можно было бы, чуть-чуть изменив, свести к синтаксису хаскелл. А как насчёт ООЯ?
В java получилось бы что-то такое:
b = new RuleBuilder()
for(String x : new Range("a", "z")) {
b.addRule(new StartWith(x),
new WriteToFile(x).unique(true));
}
b.addRule(new Or(new Contains("c"), new Contains("d")).unique(true),
new WriteToFile("Contains 'c | d'"));
b.addRule(new WordPrefixRule(3) {
Action createDefaultAction(String prefix) {
return new WriteToFile(prefix);
}
}.add("paa", new WriteToConsole("debug"), new WriteToFile("paa")));
b.process(theData);
Конечному пользователю, конечно, не отдашь, но если бы была выгода от использования текстового dsl, то от этого чуда её тоже можно было бы поиметь.
B>На самом деле, то же можно повторить и в ООП :) Разный подход будет в "настройке". B>Думаю решение на ООП будет выглядеть чуть по сложнее, т.к., на мой взгляд, функцию легче охватить взглядом и понять, чем класс ООП. B>Не говоря уже о добавлении нескольких классов / интерфейсов.
От смотрящего многое зависит. Имхо, если классы/интерфейсы не с неба брались, то ситуация аналогична ситуации со скобками в лиспе: кто-то без них жить не может, кто-то принципиально не перевариавает :)
BulatZiganshin пишет:
> итак, попробуйте решить эти задачи: > > 1) есть список слов. надо записать в файл "a" все слова, начинающиеся на > букву 'a', в файл "b" — слова на 'b' и т.д. вплоть до z
Я что=то не вижу тут особенно функционального программирования.
Декларативное — при возможности делать destructuring строк — может быть.
Тут даже функций высших порядков не применить, кажется.
;;;; -*- Mode: Lisp; Syntax: Common-Lisp; Package: fp-train-1 -*-
(defpackage #:fp-train-1 (:use :cl))
(in-package #:fp-train-1)
(defun 1-st-letter (word)
(cond ((symbolp word) (char (symbol-name word) 0))
((stringp word) (char word 0))
((characterp word) word)
(t word)))
(let ((files nil))
(defun letter-file (letter)
(let ((file (cdr (assoc letter files))))
(if file
file
(let ((file (open (string letter) :direction :output)))
(setf files (acons letter file files))
file))))
(defun finish ()
(mapc (lambda (e) (close (cdr e))) files)))
(defun cast-words (word-list)
"Casts all words in WORD-LIST by files named A, B, ... Z according to their
first letter"
(unwind-protect
(mapc
(lambda (word)
(format (letter-file (1-st-letter word)) "~A~%" word))
word-list)
(finish)))