Опять про паттерн матчинг
Jun. 18th, 2007 05:13 pmНа RSDN технические работы.
Хотел зацепиться в очередном флейме "ФВП vs паттерн-матчинг". Ребята показывали насколько ПМ сокращает код и проясняет его по сравнению с обычным императивным. А я пытался показать, насколько комбинаторы сокращают код и проясняют его по сравнению с ПМ. Ну и заодно показал пример как на основе equational reasoning можно улучшить код.
Продублирую, мне это было интересно - может ещё кому нибудь понравится.
Изначально, стояла такая задача (если я её верно понял):
Имеются вектор и двумерная матрица, надо перемножить вектор на столбцы матрицы, а затем сложить каждый столбец в одно число. Получим список чисел, количество которых равно количеству столбцов исходной матрицы, ну или длине строки матрицы.
Вообще то это подзадача, вторая подзадача там была - получение
Вот решение на Немерле с использованием паттерн-матчинга:
Удалены участки, относящиеся к вычислению
Вот первое пришедшее мне в голову решение на Haskell.
Плюс, помимо краткости и выразительности: мы можем легко сделать преобразования.
Сделать их можно, используя три закона
а также эта-редукцию и замену ($) на (.)
Всю цепочку показывать не буду - она тривиальна. Вот результат:
По моему короче, эффективнее и понятнее. Внутри сразу видно маленькую чётко очерченную подзадачку
P.S. А ещё я сегодня видел в метро парня, читавшего распечатку "Знакомство с языком Haskell" или что то в этом духе ;-)
P.P.S. А ещё я подумал, что изобрёл слово "индексикация" как перевод indexitis (болезнь использования (!!) в Haskell), а оно оказывается существует реально и означает совсем-совсем другое :-(
Хотел зацепиться в очередном флейме "ФВП vs паттерн-матчинг". Ребята показывали насколько ПМ сокращает код и проясняет его по сравнению с обычным императивным. А я пытался показать, насколько комбинаторы сокращают код и проясняют его по сравнению с ПМ. Ну и заодно показал пример как на основе equational reasoning можно улучшить код.
Продублирую, мне это было интересно - может ещё кому нибудь понравится.
Изначально, стояла такая задача (если я её верно понял):
Имеются вектор и двумерная матрица, надо перемножить вектор на столбцы матрицы, а затем сложить каждый столбец в одно число. Получим список чисел, количество которых равно количеству столбцов исходной матрицы, ну или длине строки матрицы.
Вообще то это подзадача, вторая подзадача там была - получение
init списка, его тоже зачем то решали через ПМ (в Немерле нет аналога init?).Вот решение на Немерле с использованием паттерн-матчинга:
def GetDx(_, _)
{
| ([ck], [cf]) =>
cf.Map(_ * ck)
| (ck :: klast, cf :: flast) =>
def incDx = GetDx(klast, flast);
cf.MapI((i, f) => f * ck + incDx[i])
| _ => throw Exception();//Если списки разной длинны.
}
Удалены участки, относящиеся к вычислению
init. MapI - это map с дополнительным параметром - индексом. Матрица в решении - это не список списков, как у меня на Haskell, а список массивов, так что MapI применим, правда, по мне всё таки zipWith (\x y -> ck * x + y) cf incDx понятнее.Вот первое пришедшее мне в голову решение на Haskell.
getDx ks fs = map sum $ transpose $ zipWith (\x -> map (x*)) ks fs
zipWith (\x -> map (x*)) ks fs - переменожаем ks на столбцы fsmap sum . transpose - суммируем столбцыПлюс, помимо краткости и выразительности: мы можем легко сделать преобразования.
Сделать их можно, используя три закона
1. (f . g) . h == f . (g . h) 2. map f . map g == map (f . g) 3. transpose . zipWith (map . f) xs == map (zipWith f xs) . transpose
а также эта-редукцию и замену ($) на (.)
Всю цепочку показывать не буду - она тривиальна. Вот результат:
map (sum . zipWith (*) ks) $ transpose fs
По моему короче, эффективнее и понятнее. Внутри сразу видно маленькую чётко очерченную подзадачку
(sum . zipWith (*) ks) - вектор на вектор, результат сложить. В случае паттерн-матчинга эта подзадачка раскидана по всему коду; сделать преобразование, подобное приведённому, гораздо сложнее.P.S. А ещё я сегодня видел в метро парня, читавшего распечатку "Знакомство с языком Haskell" или что то в этом духе ;-)
P.P.S. А ещё я подумал, что изобрёл слово "индексикация" как перевод indexitis (болезнь использования (!!) в Haskell), а оно оказывается существует реально и означает совсем-совсем другое :-(
Оп-па?
Date: 2007-06-18 01:59 pm (UTC)А где Вы ехали?
Впрочем, вряд ли это был я :(
Во-первых, я ехал всего один перегон (от Петровско-Разумовской до Тимирязевской) - это было около 9:00.
Во вторых, читал я распечатанный из pdf'ов сборничек статей по Хаскелю.
В третих, даже не читал, а держал в руках (задумался)... А тогда можно было разве что прочитать "History of Haskell" в заголовке первой статьи...
Re: Оп-па?
Date: 2007-06-18 02:07 pm (UTC)А так - да, я тоже с распечатками езжу иногда.
no subject
Date: 2007-06-18 04:06 pm (UTC)sum . zipWith (*) - это как раз операция, которая в формуле умножения вектора на матрицу (TeXом)
c_k=\sum_{j=1}^n a_jb_{jk}
Связываем её ks и мэппируем куда надо!
Кстати, до умножения матриц легко обобщается ещё одним map
mult ass bss = map (\xs -> getDx xs bss) ass
Надо подумать как это слить вместе покрасивее...
no subject
Date: 2007-06-18 06:33 pm (UTC)(.##) :: (a -> b) -> (c -> d -> a) -> (c -> d -> b)
(.##) = (.) (.) (.)
:-)
no subject
Date: 2007-06-18 08:38 pm (UTC)no subject
Date: 2007-06-19 03:55 am (UTC)Ага, я тоже поиграл и понял, что не пойдёт. Для работы с матрицами нужно какое-то внешнее произведение
outerProductWith :: (a->b->c)->[a]->[b]->[[c]]
расширяющее zipWith (чтобы каждый a с каждым b). Реализацию оставляем в качестве упражнения заинтересованному читателю :-)
no subject
Date: 2007-06-19 04:11 am (UTC)http://deni-ok.livejournal.com/2598.html
no subject
Date: 2007-06-18 06:31 pm (UTC)Кстати в J ПМ вообще нет, если не считать за это конструкцию одновременного присванивания значений нескольким переменным ('A B C' =: 1 ; 2 ;3). Вместо него как правило используют глаголы доступа к элементам структуры, которые потом используются в других глаголах с помощью тацитного подхода для композиции новых структур.
Что-то типа:
На мой взгляд не намного хуже ПМ. А чем-то лучше. Паттерны как бы объекты первого класса получаются.
Ну а задачка на J решается так, если я все правильно понял:
--
Mikl
no subject
Date: 2007-06-18 09:27 pm (UTC)Исторически сложилось :)
> Кстати в J ПМ вообще нет
Тацитный подход - это что то вроде pointfree, верно? Ну так это же аналогично применению комбинаторов, нет? (Я J не знаю).
Всё никак не возьмусь его поучить. Хотя бы ради того, чтобы прочесть, что такое [;];
Ведь это не смайлик? ;-)
no subject
Date: 2007-06-19 06:44 am (UTC)Ну да, pointfree. Но только еще больше фри, чем в других языках. Обычно для композиции используют функции, параметрами передают другие и унутре уже композируют как надо. Ну та же "." . А в тацитном программировании композиция задается самой структурой выражения, без привлечения специальных функций. Просто количество таких вариантов ограничено. В J их два:
1. Хук (hook) : x (f g) y -> x f (g y)
2. Вилка (fork) : x (f1 g f2) y -> (x f1 y) g (x f2 y)
Как видно все очень интуитивно. Если нам надо перемножить разность и сумму двух чисел, то пишем просто (+ * -). Практически что думаю то и пишу. Ну или классический уже пример среднего (+/ % #). Здесь +/ это сумма всех чисел вектора, % - деление, # - кол-во значений.
>Всё никак не возьмусь его поучить. Хотя бы ради того, чтобы прочесть, что такое [;];
>Ведь это не смайлик? ;-)
Да определенно ознакомится стоит. Поначалу сбивает с толку, но потом просто очаровывает. Со мной по крайней мере так было. А зубастый смайлик это опять же вилка, ведь можно писать не только (f1 + f2), но и (f1 + f2 + f3 - f4), это тоже самое, что (f1 + (f2 + (f3 - f4))). Так как вилка по сути функция, она может сразу участвовать в другом хуке или вилке. И опять же очень интуитивно, нет вспомогательных функций, не говоря уже о переменных.
Так вот функция в J может иметь два параметра - левый и правый (x и y). [ и ] - это функции, которые возвращают свой левый и правый аргумент соответственно (тут видна и попытка использовать визуальную составляющую для пояснения смысла функции). ; - это тоже функция, возвращающая список отбоксированных параметров, ну т.е. список с элементами разных типов. Обычный список имеет элементы одного типа. Я использовал конструкцию param1 ([;];res) param2 чтобы вывести сразу и входные параметры и результат. Как видишь, все очень просто.
--
Mikl
no subject
Date: 2007-06-19 08:18 am (UTC)no subject
Date: 2007-06-20 07:50 am (UTC)А с использованием внутреннего произведения всё изящно сворачивается в
На K, впрочем, это уже вообще просто издевательски просто:
Да есть и встроенная функция для такой задачи:
Но я вообще-то про другое хотел спросить. Почему, интересно, тема pattern matching всегда (по крайней мере на RSDN) возникает в связи с функциональным программированием? Вроде, если грубо «на пальцах» ФП — это про functions as first-class citizens, а pattern matching — ну это сахар такой для удобства. Казалось бы, совсем разные вещи и связь здесь мне не понятна…
(немного подумав) Возможно, здесь цепочка такая. Pattern matching хорош в случаях нетривиальной типизации и от типизации идёт уже дорожка к ФП?
no subject
Date: 2007-06-20 08:06 am (UTC)Насчёт ПМ в ФП - шут его знает, скорее какие то психологические причины. Истоки ПМ, либо подготовка автора, либо тематика форума :)
> Pattern matching хорош в случаях нетривиальной типизации и от типизации идёт уже дорожка к ФП?
Имеется в виду ПМ для алгебраических типов, алгебраические типы в ФП?
На самом деле мне кажется, что дело в том, что ПМ повышает декларативность кода. Ну, а это уже то, что часто связывают с ФП.
no subject
Date: 2007-06-20 08:54 am (UTC)>Имеется в виду ПМ для алгебраических типов, алгебраические типы в ФП?
Я имел в виду, что в языках без Hindley-Milner нельзя имитировать Haskell-овское
>На самом деле мне кажется, что дело в том, что ПМ повышает декларативность кода
Гм, ну ладно, примем это за объяснение.
no subject
Date: 2007-06-29 07:14 pm (UTC)no subject
Date: 2007-06-30 10:14 am (UTC)no subject
Date: 2007-06-30 10:23 am (UTC)no subject
Date: 2007-07-01 07:48 am (UTC)no subject
Date: 2007-07-01 10:42 am (UTC)no subject
Date: 2007-07-02 07:47 am (UTC)no subject
Date: 2007-07-02 08:23 am (UTC)no subject
Date: 2007-07-02 08:35 am (UTC)А так да, хочется порешать, там прикольные задачки.
no subject
Date: 2010-04-28 04:23 pm (UTC)Но дизайн языка с хитрыми паттернами, когда соответствие означает не только "вычислить выражение справа и вернуть результат", а "вычислить нужное количество раз, с разными параметрами, а затем что-то сделать с результатами", вполне возможен.
Это тема с очень классными возможностями, но смотреть их лучше по таким языкам как SNOBOL, РЕФАЛ и Prolog. Увы, по отдельности. К совокупности же функционала пока не приблизились ни библиотечные эксперименты на Haskell (view patterns), ни, тем более, экстракторы в Scala.
Впрочем, и комбинаторы лучше смотреть по APL/J/K. :)
no subject
Date: 2010-05-11 05:09 am (UTC)Maude видели, кстати?
Почему экстракторы в Scala "тем более"? Чем они менее мощны, чем view patterns?
no subject
Date: 2010-05-11 04:41 pm (UTC)Если взять пример node(attr("my-key", value)), то в случае с экстракторами Scala мы вынуждены будем разбить его на несколько конструкций. Ни node, ни attr не могут использовать "my-key", возможна только выдача чего-то на сравнение с этой строкой.
Если же node и attr обычные функции или конструкторы типов, а value либо запрос (как в Prolog), либо плейсхолдер (first class patterns), то задача тривиальна.
no subject
Date: 2010-05-14 06:42 am (UTC)view :: x -> Maybe y эквивалентен object View { def unapply(x: X): Option[Y] }
view -> p в одном случае и View(p) в другом.
поэтому фраза
> в случае с экстракторами Scala мы вынуждены будем разбить его на несколько конструкций. Ни node, ни attr не могут использовать "my-key", возможна только выдача чего-то на сравнение с этой строкой.
мне не понятна :(
no subject
Date: 2010-05-14 12:20 pm (UTC)no subject
Date: 2010-05-15 11:56 am (UTC)no subject
Date: 2010-04-28 04:25 pm (UTC)no subject
Date: 2010-04-29 07:26 am (UTC)no subject
Date: 2010-05-11 05:19 am (UTC)Моя мысль достаточно проста. ПМ - это нижний уровень. У него есть большой недостаток - нарушение инкапсуляции. Использовать его лучше там же, где мы определяем абстракцию. Наружу же лучше отдавать интерфейс - например, комбинаторы ;-)
Тут исторический момент - на рсдн любили поорать о том, что ФП даёт такое замечательное средство, как ПМ. Мне лень сейчас искать, но создавалось ощущение некой божественной природы ПМ. Гораздо более важные вещи - ФВП, immutability ставились на нижнюю полку. Про immutability вообще говорили, что она не нужна. Там большие флеймы были, со мной в том числе.
Пойнт поста в том, чтобы разбить аргументы адептов ПМ, показать насколько это незначительная (по сравнению с их представлением) вещь, насколько небольшие задачи она решает и насколько мешает решать большие :-)
no subject
Date: 2010-05-13 07:02 pm (UTC)Вообще-то, я как раз и предложил решить большую плохо поставленную задачу.
Твой же пример вырожден. Это как на произведении матриц демонстрировать производительность параллельных машин - всегда можно подобрать размер матрицы, где система будет работать в насыщении.
Комбинаторы могут появиться, когда на ПМ уже разобрал большую и неприятную задачу на винтики. Не раньше.
И ещё.
Ты борешься с нарушением инкапсуляции, я же предпочитаю контролировать утечки - нарушение неявно выраженных инвариантов. Правильно составленные типы данных и сравнение с образцом в этом большой помощник.
no subject
Date: 2010-05-14 06:33 am (UTC)Естественно, с этим не поспоришь. Я предлагаю разделить комбинаторы, реализованные на ПМ - описание нижнего уровня абстракции, и клиентский код, использующий эти комбинаторы - использование этой абстракции на верхнем уровне. Именно из-за того, что эти уровни разные и нужна инкапсуляция.
Кстати, часто тебе приходится список разбирать?
> Вообще-то, я как раз и предложил решить большую плохо поставленную задачу.
Я это понял, но это не задача, даже плохо поставленная. Поскольку я не могу выбрать структуру, т.к. даже приблизительно не знаю, что с этой схемой делать, то мне и ПМ здесь не помощник. Что с чем сравниваем непонятно.
Ну, сделаю я графом из стандартной библиотеки эту схему, например. Что с того?
> Ты борешься с нарушением инкапсуляции, я же предпочитаю контролировать утечки - нарушение неявно выраженных инвариантов. Правильно составленные типы данных и сравнение с образцом в этом большой помощник.
Продемонстрируй плз.
no subject
Date: 2010-05-14 07:22 am (UTC)На голову-хвост? Последнее время нет.
А вот другие структуры данных - да.
>Я это понял, но это не задача, даже плохо поставленная. Поскольку я не могу выбрать структуру, т.к. даже приблизительно не знаю, что с этой схемой делать, то мне и ПМ здесь не помощник. Что с чем сравниваем непонятно.
Вообще-то, определение данных ты дать можешь. А вот операции над ними - нет.
В самом начале любой работы примерно так и есть: мы можем дать хоть какое-то описание сущностям, но не в силах полностью задать все операции, не говоря уж о базовых.
>Продемонстрируй плз.
Я только учусь (http://personal.cis.strath.ac.uk/~conor/pub/she/faking.html).
В ближайшем ПФП будет наша статья насчёт задания и поддержания инвариантов.
no subject
Date: 2010-05-16 08:40 pm (UTC)Свои? ;)
Определение данных я дать не могу, пока не знаю, что за задача - мы по графу будет рисовать картинку? Нам нужно знать линии, по которым будут ездить поезда? Нам нужно знать время передвижения между станциями? Нужно ли учитывать переходы? Будет ли эта схема снаружи привязываться к карте города? и т.д.
"В самом начале работы" зависит от характера работы, наверное. Я стараюсь описывать сначала операции, это отвязывает меня от реализации и позволяет потом дорисовать типы - как проекцию денотационной семантики на код. Во-вторых, это позволяет видеть, какими математическими объектами является описываемая сущность. Т.е. я изначально стараюсь определить язык, с которым буду работать. А это и есть комбинаторы.
SHE знаю. конечно. Очень клёвая вещь, но так и не попробовал. Я понимаю что ты имеешь в виду, и согласен с этим. Но мы говорим о разных уровнях, IMHO.