lomeo: (лямбда)
Dmitry Antonyuk ([personal profile] lomeo) wrote2007-06-18 05:13 pm

Опять про паттерн матчинг

На 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), а оно оказывается существует реально и означает совсем-совсем другое :-(

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting