Здравствуйте, 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