lomeo: (лямбда)
[personal profile] lomeo
На RSDN технические работы.


Хотел зацепиться в очередном флейме "ФВП 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 на столбцы fs
map 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)
From: [identity profile] http://users.livejournal.com/_dusty_/
> P.S. А ещё я сегодня видел в метро парня, читавшего распечатку "Знакомство с языком Haskell" или что то в этом духе ;-)
А где Вы ехали?

Впрочем, вряд ли это был я :(
Во-первых, я ехал всего один перегон (от Петровско-Разумовской до Тимирязевской) - это было около 9:00.
Во вторых, читал я распечатанный из pdf'ов сборничек статей по Хаскелю.
В третих, даже не читал, а держал в руках (задумался)... А тогда можно было разве что прочитать "History of Haskell" в заголовке первой статьи...

Re: Оп-па?

Date: 2007-06-18 02:07 pm (UTC)
From: [identity profile] lomeo.livejournal.com
Тогда нет :) По зеленой ветке на север, в пределах кольца.
А так - да, я тоже с распечатками езжу иногда.

Date: 2007-06-18 04:06 pm (UTC)
From: [identity profile] deni-ok.livejournal.com
Здорово! ПМ здесь сливает без вариантов :)

sum . zipWith (*) - это как раз операция, которая в формуле умножения вектора на матрицу (TeXом)
c_k=\sum_{j=1}^n a_jb_{jk}
Связываем её ks и мэппируем куда надо!

Кстати, до умножения матриц легко обобщается ещё одним map
mult ass bss = map (\xs -> getDx xs bss) ass

Надо подумать как это слить вместе покрасивее...

Date: 2007-06-18 06:33 pm (UTC)
From: [identity profile] deni-ok.livejournal.com
Имел ввиду не sum . zipWith (*), которая, конечно, неправильна без аргумента, а sum .## zipWith (*)

(.##) :: (a -> b) -> (c -> d -> a) -> (c -> d -> b)
(.##) = (.) (.) (.)

:-)

Date: 2007-06-18 08:38 pm (UTC)
From: [identity profile] lomeo.livejournal.com
Прикольно! А map в map-е хрен сольёшь, можно разве что внутренний наружу высунуть, а внешний внутрь, только смысла не вижу, вроде там ничего не сделаешь.

Date: 2007-06-19 03:55 am (UTC)
From: [identity profile] deni-ok.livejournal.com
>А map в map-е хрен сольёшь

Ага, я тоже поиграл и понял, что не пойдёт. Для работы с матрицами нужно какое-то внешнее произведение

outerProductWith :: (a->b->c)->[a]->[b]->[[c]]

расширяющее zipWith (чтобы каждый a с каждым b). Реализацию оставляем в качестве упражнения заинтересованному читателю :-)

Date: 2007-06-19 04:11 am (UTC)
From: [identity profile] deni-ok.livejournal.com
Видел мои издевательства над рядом Тэйлора?

http://deni-ok.livejournal.com/2598.html

Date: 2007-06-18 06:31 pm (UTC)
From: [identity profile] mkurkov.livejournal.com
Странное это занятие сравнивать ПМ и комбинаторы. По-моему они никак не пересекаются. А ПМ так просто средство декомпозиции структур. Что-то не понимаю смысла спора.
Кстати в J ПМ вообще нет, если не считать за это конструкцию одновременного присванивания значений нескольким переменным ('A B C' =: 1 ; 2 ;3). Вместо него как правило используют глаголы доступа к элементам структуры, которые потом используются в других глаголах с помощью тацитного подхода для композиции новых структур.
Что-то типа:
Field1 =: get_element&1
Field2 =: get_element&2
Field3 =: get_element&3
NewStruct =: Field1; (Field2 + Field3) ; Field3
NS1 =: NewStruct OldStruct1

На мой взгляд не намного хуже ПМ. А чем-то лучше. Паттерны как бы объекты первого класса получаются.
Ну а задачка на J решается так, если я все правильно понял:
res =: +/ @: *
1 2 3 4 5 ([;]; res)  i. 5 4
┌─────────┬───────────┬───────────────┐
│1 2 3 4 5│ 0  1  2  3│160 175 190 205│
│         │ 4  5  6  7│               │
│         │ 8  9 10 11│               │
│         │12 13 14 15│               │
│         │16 17 18 19│               │
└─────────┴───────────┴───────────────┘

--
Mikl

Date: 2007-06-18 09:27 pm (UTC)
From: [identity profile] lomeo.livejournal.com
> Странное это занятие сравнивать ПМ и комбинаторы

Исторически сложилось :)

> Кстати в J ПМ вообще нет

Тацитный подход - это что то вроде pointfree, верно? Ну так это же аналогично применению комбинаторов, нет? (Я J не знаю).

Всё никак не возьмусь его поучить. Хотя бы ради того, чтобы прочесть, что такое [;];
Ведь это не смайлик? ;-)

Date: 2007-06-19 06:44 am (UTC)
From: [identity profile] mkurkov.livejournal.com
>Тацитный подход - это что то вроде pointfree, верно? Ну так это же аналогично применению комбинаторов, >нет? (Я J не знаю).

Ну да, 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

Date: 2007-06-19 08:18 am (UTC)
From: [identity profile] lomeo.livejournal.com
Восхитительно! Спасибо огромное за разъяснения, теперь я обязательно попробую J.

Date: 2007-06-20 07:50 am (UTC)
From: [identity profile] bulg.livejournal.com
Есть некоторое впечатление, что для задач подобного сорта лучше подойдут языки из семейства APL. И потому, что они изначально ориентированы на работу с матрицами, и потому что внутреннее произведение (которое здесь очень кстати) встроено в язык. Вот первое решение на APL «в лоб», которое сразу приходит в голову (здесь v и m — заданные вектор и матрица соответственно):

Image

А с использованием внутреннего произведения всё изящно сворачивается в

Image

На K, впрочем, это уже вообще просто издевательски просто:

+/v*m

Да есть и встроенная функция для такой задачи:

v _dot m

Но я вообще-то про другое хотел спросить. Почему, интересно, тема pattern matching всегда (по крайней мере на RSDN) возникает в связи с функциональным программированием? Вроде, если грубо «на пальцах» ФП — это про functions as first-class citizens, а pattern matching — ну это сахар такой для удобства. Казалось бы, совсем разные вещи и связь здесь мне не понятна…

(немного подумав) Возможно, здесь цепочка такая. Pattern matching хорош в случаях нетривиальной типизации и от типизации идёт уже дорожка к ФП?

Date: 2007-06-20 08:06 am (UTC)
From: [identity profile] lomeo.livejournal.com
Про APL/K спасибо. Тут, кстати уже приводили решение на J.

Насчёт ПМ в ФП - шут его знает, скорее какие то психологические причины. Истоки ПМ, либо подготовка автора, либо тематика форума :)

> Pattern matching хорош в случаях нетривиальной типизации и от типизации идёт уже дорожка к ФП?
Имеется в виду ПМ для алгебраических типов, алгебраические типы в ФП?

На самом деле мне кажется, что дело в том, что ПМ повышает декларативность кода. Ну, а это уже то, что часто связывают с ФП.

Date: 2007-06-20 08:54 am (UTC)
From: [identity profile] bulg.livejournal.com
>> Pattern matching хорош в случаях нетривиальной типизации и от типизации идёт уже дорожка к ФП?
>Имеется в виду ПМ для алгебраических типов, алгебраические типы в ФП?

Я имел в виду, что в языках без Hindley-Milner нельзя имитировать Haskell-овское

foo _ = 42

>На самом деле мне кажется, что дело в том, что ПМ повышает декларативность кода

Гм, ну ладно, примем это за объяснение.

Date: 2007-06-29 07:14 pm (UTC)
From: [identity profile] jerom.livejournal.com
А про P.S., где именно в метро и во сколько видел парня с этой распечаткой? :-)

Date: 2007-06-30 10:14 am (UTC)
From: [identity profile] lomeo.livejournal.com
В центре на зеленой ветке по направлению к Речному Вокзалу. Около 10-11.

Date: 2007-06-30 10:23 am (UTC)
From: [identity profile] jerom.livejournal.com
А, я думал меня видел. Я в это время по серой ехал :-)

Date: 2007-07-01 07:48 am (UTC)
From: [identity profile] lomeo.livejournal.com
Сколько же вас! :) Получается, где бы я ни ехал, всё равно на кого нибудь бы нарвался ;)

Date: 2007-07-01 10:42 am (UTC)
From: [identity profile] deni-ok.livejournal.com
Это, наверное, был флэш-моб ;)

Date: 2007-07-02 07:47 am (UTC)

Date: 2007-07-02 08:23 am (UTC)
From: [identity profile] jerom.livejournal.com
Дас, нас много. А что, http://projecteuler.net просто так заброшен или что-то лучше найдено?

Date: 2007-07-02 08:35 am (UTC)
From: [identity profile] lomeo.livejournal.com
Да блин времени нет :-(
А так да, хочется порешать, там прикольные задачки.

Date: 2010-04-28 04:23 pm (UTC)
From: [identity profile] justy-tylor.livejournal.com
Pattern matching - способ выделить данные путём описания какого-либо из представлений их структуры. Для решения вышеописанной задачи в Haskell (и в Nemerle) не подходит.

Но дизайн языка с хитрыми паттернами, когда соответствие означает не только "вычислить выражение справа и вернуть результат", а "вычислить нужное количество раз, с разными параметрами, а затем что-то сделать с результатами", вполне возможен.

Это тема с очень классными возможностями, но смотреть их лучше по таким языкам как SNOBOL, РЕФАЛ и Prolog. Увы, по отдельности. К совокупности же функционала пока не приблизились ни библиотечные эксперименты на Haskell (view patterns), ни, тем более, экстракторы в Scala.

Впрочем, и комбинаторы лучше смотреть по APL/J/K. :)

Date: 2010-05-11 05:09 am (UTC)
From: [identity profile] lomeo.livejournal.com
SNOBOL посмотрю, спасибо. Об остальном имею представление, достаточное для того, чтобы понять, что вы пишете :)

Maude видели, кстати?

Почему экстракторы в Scala "тем более"? Чем они менее мощны, чем view patterns?

Date: 2010-05-11 04:41 pm (UTC)
From: [identity profile] justy-tylor.livejournal.com
Видимо собирался написать view patterns и first class patterns. :)

Если взять пример node(attr("my-key", value)), то в случае с экстракторами Scala мы вынуждены будем разбить его на несколько конструкций. Ни node, ни attr не могут использовать "my-key", возможна только выдача чего-то на сравнение с этой строкой.

Если же node и attr обычные функции или конструкторы типов, а value либо запрос (как в Prolog), либо плейсхолдер (first class patterns), то задача тривиальна.

Date: 2010-05-14 06:42 am (UTC)
From: [identity profile] lomeo.livejournal.com
что-то я туплю.

view :: x -> Maybe y эквивалентен object View { def unapply(x: X): Option[Y] }

view -> p в одном случае и View(p) в другом.

поэтому фраза

> в случае с экстракторами Scala мы вынуждены будем разбить его на несколько конструкций. Ни node, ни attr не могут использовать "my-key", возможна только выдача чего-то на сравнение с этой строкой.

мне не понятна :(

Date: 2010-05-14 12:20 pm (UTC)
From: [identity profile] justy-tylor.livejournal.com
Моё сравнение не с view patterns, а с first class patterns. В них можно перенести даже функционал селекторов XPath. С экстракторами это принципиально невозможно, мы можем распаковать данные и сравнить с параметрами паттерна, но напрямую они недоступны.

Date: 2010-05-15 11:56 am (UTC)
From: [identity profile] lomeo.livejournal.com
А. Ясно.

Date: 2010-04-28 04:25 pm (UTC)
From: [identity profile] justy-tylor.livejournal.com
Обратил внимание на дату поста. Чёто лента fprog глючит с апдейтами. ;)

Date: 2010-04-29 07:26 am (UTC)
From: [identity profile] thesz.livejournal.com
Попробуй комбинаторами описать схему Московского Метро. ;)

Date: 2010-05-11 05:19 am (UTC)
From: [identity profile] lomeo.livejournal.com
Для чего? То есть, что потом с этой схемой делать предполагается?

Моя мысль достаточно проста. ПМ - это нижний уровень. У него есть большой недостаток - нарушение инкапсуляции. Использовать его лучше там же, где мы определяем абстракцию. Наружу же лучше отдавать интерфейс - например, комбинаторы ;-)

Тут исторический момент - на рсдн любили поорать о том, что ФП даёт такое замечательное средство, как ПМ. Мне лень сейчас искать, но создавалось ощущение некой божественной природы ПМ. Гораздо более важные вещи - ФВП, immutability ставились на нижнюю полку. Про immutability вообще говорили, что она не нужна. Там большие флеймы были, со мной в том числе.

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

Date: 2010-05-13 07:02 pm (UTC)
From: [identity profile] thesz.livejournal.com
>Пойнт поста в том, чтобы разбить аргументы адептов ПМ, показать насколько это незначительная (по сравнению с их представлением) вещь, насколько небольшие задачи она решает и насколько мешает решать большие :-)

Вообще-то, я как раз и предложил решить большую плохо поставленную задачу.

Твой же пример вырожден. Это как на произведении матриц демонстрировать производительность параллельных машин - всегда можно подобрать размер матрицы, где система будет работать в насыщении.

Комбинаторы могут появиться, когда на ПМ уже разобрал большую и неприятную задачу на винтики. Не раньше.

И ещё.

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

Date: 2010-05-14 06:33 am (UTC)
From: [identity profile] lomeo.livejournal.com
> Комбинаторы могут появиться, когда на ПМ уже разобрал большую и неприятную задачу на винтики. Не раньше.

Естественно, с этим не поспоришь. Я предлагаю разделить комбинаторы, реализованные на ПМ - описание нижнего уровня абстракции, и клиентский код, использующий эти комбинаторы - использование этой абстракции на верхнем уровне. Именно из-за того, что эти уровни разные и нужна инкапсуляция.

Кстати, часто тебе приходится список разбирать?

> Вообще-то, я как раз и предложил решить большую плохо поставленную задачу.

Я это понял, но это не задача, даже плохо поставленная. Поскольку я не могу выбрать структуру, т.к. даже приблизительно не знаю, что с этой схемой делать, то мне и ПМ здесь не помощник. Что с чем сравниваем непонятно.

Ну, сделаю я графом из стандартной библиотеки эту схему, например. Что с того?

> Ты борешься с нарушением инкапсуляции, я же предпочитаю контролировать утечки - нарушение неявно выраженных инвариантов. Правильно составленные типы данных и сравнение с образцом в этом большой помощник.

Продемонстрируй плз.

Date: 2010-05-14 07:22 am (UTC)
From: [identity profile] thesz.livejournal.com
>Кстати, часто тебе приходится список разбирать?

На голову-хвост? Последнее время нет.

А вот другие структуры данных - да.

>Я это понял, но это не задача, даже плохо поставленная. Поскольку я не могу выбрать структуру, т.к. даже приблизительно не знаю, что с этой схемой делать, то мне и ПМ здесь не помощник. Что с чем сравниваем непонятно.

Вообще-то, определение данных ты дать можешь. А вот операции над ними - нет.

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

>Продемонстрируй плз.

Я только учусь (http://personal.cis.strath.ac.uk/~conor/pub/she/faking.html).

В ближайшем ПФП будет наша статья насчёт задания и поддержания инвариантов.

Date: 2010-05-16 08:40 pm (UTC)
From: [identity profile] lomeo.livejournal.com
> А вот другие структуры данных - да.

Свои? ;)

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

"В самом начале работы" зависит от характера работы, наверное. Я стараюсь описывать сначала операции, это отвязывает меня от реализации и позволяет потом дорисовать типы - как проекцию денотационной семантики на код. Во-вторых, это позволяет видеть, какими математическими объектами является описываемая сущность. Т.е. я изначально стараюсь определить язык, с которым буду работать. А это и есть комбинаторы.

SHE знаю. конечно. Очень клёвая вещь, но так и не попробовал. Я понимаю что ты имеешь в виду, и согласен с этим. Но мы говорим о разных уровнях, IMHO.

Profile

lomeo: (Default)
Dmitry Antonyuk

September 2025

S M T W T F S
 123456
78910111213
14 151617181920
21222324252627
282930    

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Feb. 27th, 2026 04:39 am
Powered by Dreamwidth Studios