Re[36]: Есть ли вещи, которые вы прницпиально не понимаете...
От: Klapaucius  
Дата: 11.02.14 09:25
Оценка:
Здравствуйте, alex_public, Вы писали:

_>Ну так это же не ФЯ с его ограничениями на иммутабельность — мы можем создавать что угодно, не только возвращая значения из функций. )


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

_>Шаблоны проектирования прекрасно реализуются и без всякого МП, так что оно собственно тут ни при чём. Так вот, возвращаясь к шаблонам проектирования... Это разве на ваш взгляд не абстракции достаточно высокого уровня?


Нет, не реализуются. Без метапрограммирования никаких шаблонов проектирования в коде нет — только результат их применения в голове программиста. Поэтому к уровню кода они никакого значения не имеют.

_>Вот например возьмём такое общеизвестное простейшее приложение как "Блокнот". Из каких математических объектов он будет состоять, если мы его реализуем по высокоуровневому? )


Из функций, категорий, моноидов, функторов, монад и т.д.

_>Нуну)


_>Возьмём например такой простейший код, работающий через константные ссылки:

_>
_>prepare :: Array Int Int -> Array Int Int
_>prepare а = а//[(1, а!1*а!1)]

_>test :: Array Int Int -> Int
_>test а = let r=prepare а
_>        in sum $ elems r
 
_>main = print $ test $ listArray (0, length l -1) l
_>        where l= [1, 2, 3]
_>


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

prepare a = a // [(1, a!1^2)]
   
test = V.sum . prepare

main = print . test . V.fromList $ [1..3::Int]


_>Его прямым аналогом на C++ будет что-то вроде:


Нет, прямой аналог этого кода на C++ написать нельзя, потому что системы контроля эффектов в нем нет.

_>
_>auto prepare(const vector<int>& a)
_>{
_>    vector<int> r=a;
_>    r[1]=r[1]*r[1];
_>    return r;
_>}

_>auto test(const vector<int>& a)
_>{
_>    auto r=prepare(a);
_>    return accumulate(r.cbegin(), r.cend(), 0);
_>}

_>int main(){cout<<test({1, 2, 3});}
_>


_>А теперь предположим, что нам захотелось переделать это под неконстантные ссылки. Кстати, вполне реальная задачка, если у нас массив не из 3-ёх элементов, а из 3*10^9. На C++ это станет выглядеть так:

_>
_>void prepare(vector<int>& a){a[1]=a[1]*a[1];}

_>auto test(vector<int>&& a)
_>{
_>    prepare(a);
_>    return accumulate(a.cbegin(), a.cend(), 0);
_>}

_>int main(){cout<<test({1, 2, 3});}
_>

_>Видно, что код практически не изменился по своей структуре, а стал только проще — просто стёрли лишнее.

Ну так потому что код один и тот же. Странно ожидать, что один и тот же код будет выглядеть по разному.

_>Ну и теперь полюбуемся, как будет выглядеть что-то подобное на Хаскеле:

_>
_>prepare :: STArray s Int Int -> ST s ()
_>prepare a = do
_>    x <- readArray a 1
_>    writeArray a 1 (x*x)

_>test :: STArray s Int Int -> ST s Int
_>test a = do
_>    prepare a
_>    l <- getElems a
_>    return $ sum l

_>main = print $ runST $ do
_>    let l=[1, 2, 3]
_>    a <- newListArray (0, length l -1) l :: ST s (STArray s Int Int)
_>    test a
_>


prepare a = do
    x <- M.read a 1
    M.write a 1 (x*x)
    return a

test a = fmap V.sum (V.unsafeFreeze =<< prepare a)

main = print =<< test =<< (V.thaw . V.fromList $ [1..3::Int])


Что характерно, этот код отличается по скорости от первого моего примера на ~10% (если вектора взять по 100M элементов и преобразовывать не из списка, иначе измеряемое различие вообще не получить), т.е в "наивном" коде не убрано оптимизатором одно единственное лишнее копирование вектора.

_>И это ещё в лучше случае, если getElems в реальности создаёт ссылку, а не выделяет память (я просто не в курсе таких тонкостей). А если getElems выделяет память, то её использовать нельзя и придётся ещё самим писать новую версию sum, которая умеет работать с типом STArray s Int Int.


Вообще говоря, преобразовывать в список ленивый образом, "без копирования" небезопасно. Список можно проходить частями и никто не гарантирует, что изменяемый массив за это время не поменяется. Собственно поэтому в моем коде unsafeFreeze — я-то знаю, что собираюсь потребить все данные прямо тут, в функции sum, а компилятор нет. Именно этим копированием второй вариант от наивной реализации и отличается — если вместо unsafeFreeze написать freeze при преобразовании добавится копирование и разницы между вариантами не будет. Конечно, плохо, что для мутабельных массивов нет такого же богатого набора функций как и для иммутабельных, но это библиотечная проблема.

_>Но даже если забыть про этот момент, то в целом видно, что изначальный хаскельный код при переходе к монадному (который необходим для существования неконстантных ссылок) изменился до неузнаваемости.


Я вроде бы сразу признал, что преобразование между "нелифтнутым" и "лифтнутым" кодом требует значительной модификации кода. Речь-то была о сравнении "лифтнутого" и кода на обычном императивном языке.

_>Причём код не просто полностью изменился, а стал заметно страшнее — смысловая часть в нём погребена под кучей монадного синтаксического мусора.


Конечно он стал страшнее, по сравнению с "наивной" версией. Но не страшнее плюсовой, синтаксический мусор в которой существует вовсе без всякого смысла и оправдания.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.