По поводу ограничений, в Haskell даже Data.Dynamic есть, какие ограничения? Хоть динамику используйте.
Правда практика показывает, что нужно это ну очень редко. Но если очень нужно — то есть.
Просто переходя с языков, где всё цветёт мутабельностью, по первости непривычно, да, но это не потому, что Haskell плохой, а потому, что инструмент надо сначала изучить, а не кидаться им сразу гвозди забивать, а потом удивляться, что в руке-то не молоток.
Ну если это для вас действительно лишняя сущность, то значит у нас слишком разные взгляды. От неявных преобразований тоже немало ошибок возникает, а ничего плохого в явном касте (очевидном, в первую очередь, для читающего код) я не вижу. А вот в неявном — вижу. Где там floor произойдет? Для этого при чтении в уме надо типы держать, а не просто читать явное floor b + c.
В D2 тоже по типу функции можно определить некоторые аспекты, такие как pure, nothrow или safe. Обошлись как-то без ограничений.
А асинхронный код как синхронный (плоско) там можно писать?
А ФВП позволяет указать, что принимаемая ей функция pure и nothrow (и соот-но проверит ли это компилятор)?
Нет никаких ограничений, вы вполне вольны написать foo :: MVar Int -> MVar Int -> IO (MVar Int)
Тут и мутабельность, и ввод-вывод.
Если пугает синтаксис, ну так можно ж упросить, это непринципиально.
Ради шутки на Haskell как-то написали модуль, с использованием которого можно писать прямо как на обычном императивном, с циклами, присваиваниями и т.п.
Только это ведь не нужно никому.
Ограничения на практике оказываются полезными.
То, что мутабельный стейт-фул код писать немного напряжнее — это ж хорошо! Задаёт верные приоритеты. Никто не мешает его писать, но:
1. Это явно отражено в типе функции и читатель сразу видит контракты
2. Провоцирует писать более чистый код с декомпозицией (что упрощает, например, тестирование, так как набор чистых функций тестировать — сказка)
3. «Грязный» код пишется лишь тогда, когда действительно нужен, а не везде, где ни попадя.
Ну например строгая статическая типизация, по-вашему, это лишняя сущность и бессмысленное ограничение для программиста?
По типу функции Haskell можно видеть не только типы аргументов и результата, но и некоторые важные аспекты функции, наличие состояния, использование ввода-вывода и т.п. Языки с зависимыми типами идут и того дальше, позволяя зафиксировать в типе функции например факт того, что она именно сортирует список, а не делает что-либо ещё.
Причём на существующую систему типов ложатся и исключения, и ввод-вывод, и множественные результаты, и то многое другое, ради чего в других языках городятся специальные конструкции.
Но при этом лишние сущности почему-то именно в Haskell.
В заданном лично вам контексте — может быть. Но речь не только о вас.
А строгая функциональность — это большой аргумент «за». Но понять это можно только попрограммировав на Haskell.
Я не говорил «сравнимым». Ряд особенностей Haskell куда больше, поэтому не изучить и игнорировать его было бы глупо. А уж выберет ли его потом программист с опытом на C/C++, дело программиста. Я лично выбрал, и далеко не только я (почти все Haskell'исты с C/C++ background'ом).
Небольшой оффтоп:
Всё же Variadic templates несколько другое.
Между ними разница примерно как между C++ функцией template <class T>
std::vector<S> foo(std::vector<T> const & x, std::function<S(T)> const & f);
И Haskell функцией map :: [a] -> (a -> b) -> [b]
В первом случае имеем множество функций для каждого набора параметров, во втором — одна функция.
Если вы еще не знакомы с языками с зависимыми типами (dependent types), крайне рекомендую. Они до практики пока никак не доходят, но там всё очень интересно.
> Опасно её опасное использование
Это примерно как опасно опасное использование мышьяка. Вроде бы и да, конечно, но при этом все прекрасно понимают фразу «мышьяк опасен для здоровья» (причём именно как «опасно опасное использование»), и никто не трактует её как «опасно смотреть на мышьяк».
Если уж касаться функций, то «функция опасна» намекает на наличие UB и высокую вероятность допустить сложнодиагностируемую ошибку.
Вот например у какой-нить тривиальной std::less этой проблемы нет и функция неопасна.
А взять уже хотя бы std::swap, то надо, например, знать, что он использует оператор =, и потому нельзя имплементировать operator = через default std::swap, а для начала написать конкретный. И вот тут кроется опасность.
> Не может в принципе существовать функции, которая исключает её неверное использование
Ну и к каким последствиям может привести «кривой» вызов printf из Cayenne?
Напомню, у printf в описании «Writes to the standard output (stdout) a sequence of data formatted as the format argument specifies», и вот сишный printf не соответствует этой спецификации (так как на деле может далеко не только вывести что-то на экран), если передавать неверные аргументы. А printf из Cayenne соответствует всегда. И поэтому исключает неверное использование.
printf не предназначен для buffer-overrun, в спецификации этого нет (это считается UB), однако способен привести к таким последствиям.
Единственное, что может не так сделать Cayenne'вский printf — вывести не то, что вы хотели, а то, что написали, но это уже исключительно ваша ошибка, потому что эффект функции полностью соответствует спецификации.
> Опасно её опасное использование, а не функция вообще
Терминологический спор у нас сейчас получится. Это очевидно. Однако опасное использование printf куда как более вероятно, чем опасное использование std::cout, и именно об этом речь. Не о чёрном и белом, а об оттенках серого.
> И таки да, в qsort ничего плохого нет
Ничего плохого нет вообще ни в чём, это понятие сугубо субъективное.
> Не умеете пользоваться — так изучите же, в конце-то концов
Кто вам сказал, что я не умею пользоваться. Тем не менее люди продолжают изобретать всё новые способы статического контроля, до dependent types уже докатились и proof assistants. Небось не знают, что надо просто «изучить же».
Кстати, как вы думаете, статический контроль типов нужен? И если нужен, то зачем? Или динамики достаточно?
> printf не является такой функцией.
Да я даже у опытных людей встречал printf(str) вместо printf("%s", str); Можно, конечно, ответить, что и среди опытных дураки бывают, но вот почему-то неверного использования std::cout я не встречал, хотя, наверное, можно отыскать.
> Кто не знает, отстрелит себе ночу даже палкой, которая стреляет раз в год.
Ага, и функция qsort хорошая, крутые перцы же не ошибаются никогда. И C++ std::sort никому не нужен, ну разве только для инлайна, а static check нам не нужен. void qsort ( void * base, size_t num, size_t size, int ( * comparator ) ( const void *, const void * ));
Хорошая функция исключает неверное её использование. Вообще. Другое дело, что это почти всегда недостижимый идеал, но это не значит, что теперь надо вообще отказаться от движения в эту сторону.
Пройдите по ссылке-то. Там printf, но его _нельзя вызвать неверно_. Даже в треде упоминаются варнинги GCC. Т.е. команда GCC признаёт опасность функции и так или иначе пытается это решить, а вы — нет.
Ну зачем из одной крайности в другую-то. Опасность функции не имеет смысла без контекста, т.е. программистов, её использующих. Потому что именно неправильное использование программистами делает её «опасной». И вот тот факт, что функция легко может быть использована средним разработчиком неверно делает её использование нежелательным. Есть непродуктивные варианты вроде хаяния программистов, есть продуктивные вроде доведения уровня профессиональности программистов и тестирования до совершенства либо использования функций, с которыми ошибиться сложнее при том же уровне программистов и том же тестировании. Ну и второй вариант как-то попроще оказывается. Вот потому и говорят, что «лучше используйте нечто иное», хотя вы вольны, конечно, сказать, «я лучше найму гениев». Не забывайте только, что не Боги горшки обжигают.
Посмотрите, кстати, на типизированный printf из Cayenne (язык с зависимыми типами).
Ну, виноваты, конечно, пользователи, но это не значит, что ничего не надо менять.
Если проводить аналогию с обычными магазинами, то их всё же заставляют давать какие-то гарантии (и вряд ли все согласятся, что это плохо, пусть и на это уходят деньги), и будь у меня выбор ходить в магазин, где всё дозволено, и магазин, который не имеет права ставить на полки просроченный продукт и продавать в продуктовом отделе ядовитые грибы, то я пойду во второй.
Насколько я понимаю, в обсуждаемом случае маркет один, и выбора нет.
Т.е. я считаю, что люди сами дураки, но не считаю, что не стоит менять политику. Маркет должен предоставлять товар надлежащего качества (кстати троян лежал в разделе «трояны»?), а ответственность должен нести магазин. Либо маркетов должно быть много.
Правда практика показывает, что нужно это ну очень редко. Но если очень нужно — то есть.
Просто переходя с языков, где всё цветёт мутабельностью, по первости непривычно, да, но это не потому, что Haskell плохой, а потому, что инструмент надо сначала изучить, а не кидаться им сразу гвозди забивать, а потом удивляться, что в руке-то не молоток.
Ну если это для вас действительно лишняя сущность, то значит у нас слишком разные взгляды. От неявных преобразований тоже немало ошибок возникает, а ничего плохого в явном касте (очевидном, в первую очередь, для читающего код) я не вижу. А вот в неявном — вижу. Где там floor произойдет? Для этого при чтении в уме надо типы держать, а не просто читать явное floor b + c.
А асинхронный код как синхронный (плоско) там можно писать?
А ФВП позволяет указать, что принимаемая ей функция pure и nothrow (и соот-но проверит ли это компилятор)?
Нет никаких ограничений, вы вполне вольны написать
foo :: MVar Int -> MVar Int -> IO (MVar Int)Тут и мутабельность, и ввод-вывод.
Если пугает синтаксис, ну так можно ж упросить, это непринципиально.
Ради шутки на Haskell как-то написали модуль, с использованием которого можно писать прямо как на обычном императивном, с циклами, присваиваниями и т.п.
Только это ведь не нужно никому.
Ограничения на практике оказываются полезными.
То, что мутабельный стейт-фул код писать немного напряжнее — это ж хорошо! Задаёт верные приоритеты. Никто не мешает его писать, но:
1. Это явно отражено в типе функции и читатель сразу видит контракты
2. Провоцирует писать более чистый код с декомпозицией (что упрощает, например, тестирование, так как набор чистых функций тестировать — сказка)
3. «Грязный» код пишется лишь тогда, когда действительно нужен, а не везде, где ни попадя.
По типу функции Haskell можно видеть не только типы аргументов и результата, но и некоторые важные аспекты функции, наличие состояния, использование ввода-вывода и т.п. Языки с зависимыми типами идут и того дальше, позволяя зафиксировать в типе функции например факт того, что она именно сортирует список, а не делает что-либо ещё.
Причём на существующую систему типов ложатся и исключения, и ввод-вывод, и множественные результаты, и то многое другое, ради чего в других языках городятся специальные конструкции.
Но при этом лишние сущности почему-то именно в Haskell.
Вы много писали на Haskell?
Странно говорить это C++ программисту, вы не находите?
А строгая функциональность — это большой аргумент «за». Но понять это можно только попрограммировав на Haskell.
Если выбор и не поменяете, то время проведёте весело :)
Солнце, этанол, древесная (боже мой!) пыль!
Небольшой оффтоп:
Всё же Variadic templates несколько другое.
Между ними разница примерно как между C++ функцией
template <class T>
std::vector<S> foo(std::vector<T> const & x, std::function<S(T)> const & f);
И Haskell функцией
map :: [a] -> (a -> b) -> [b]
В первом случае имеем множество функций для каждого набора параметров, во втором — одна функция.
Если вы еще не знакомы с языками с зависимыми типами (dependent types), крайне рекомендую. Они до практики пока никак не доходят, но там всё очень интересно.
Это примерно как опасно опасное использование мышьяка. Вроде бы и да, конечно, но при этом все прекрасно понимают фразу «мышьяк опасен для здоровья» (причём именно как «опасно опасное использование»), и никто не трактует её как «опасно смотреть на мышьяк».
Если уж касаться функций, то «функция опасна» намекает на наличие UB и высокую вероятность допустить сложнодиагностируемую ошибку.
Вот например у какой-нить тривиальной std::less этой проблемы нет и функция неопасна.
А взять уже хотя бы std::swap, то надо, например, знать, что он использует оператор =, и потому нельзя имплементировать operator = через default std::swap, а для начала написать конкретный. И вот тут кроется опасность.
Ну и к каким последствиям может привести «кривой» вызов printf из Cayenne?
Напомню, у printf в описании «Writes to the standard output (stdout) a sequence of data formatted as the format argument specifies», и вот сишный printf не соответствует этой спецификации (так как на деле может далеко не только вывести что-то на экран), если передавать неверные аргументы. А printf из Cayenne соответствует всегда. И поэтому исключает неверное использование.
printf не предназначен для buffer-overrun, в спецификации этого нет (это считается UB), однако способен привести к таким последствиям.
Единственное, что может не так сделать Cayenne'вский printf — вывести не то, что вы хотели, а то, что написали, но это уже исключительно ваша ошибка, потому что эффект функции полностью соответствует спецификации.
> Опасно её опасное использование, а не функция вообще
Терминологический спор у нас сейчас получится. Это очевидно. Однако опасное использование printf куда как более вероятно, чем опасное использование std::cout, и именно об этом речь. Не о чёрном и белом, а об оттенках серого.
> И таки да, в qsort ничего плохого нет
Ничего плохого нет вообще ни в чём, это понятие сугубо субъективное.
> Не умеете пользоваться — так изучите же, в конце-то концов
Кто вам сказал, что я не умею пользоваться. Тем не менее люди продолжают изобретать всё новые способы статического контроля, до dependent types уже докатились и proof assistants. Небось не знают, что надо просто «изучить же».
Кстати, как вы думаете, статический контроль типов нужен? И если нужен, то зачем? Или динамики достаточно?
Да я даже у опытных людей встречал printf(str) вместо printf("%s", str); Можно, конечно, ответить, что и среди опытных дураки бывают, но вот почему-то неверного использования std::cout я не встречал, хотя, наверное, можно отыскать.
> Кто не знает, отстрелит себе ночу даже палкой, которая стреляет раз в год.
Ага, и функция qsort хорошая, крутые перцы же не ошибаются никогда. И C++ std::sort никому не нужен, ну разве только для инлайна, а static check нам не нужен.
void qsort ( void * base, size_t num, size_t size, int ( * comparator ) ( const void *, const void * ));
Хорошая функция исключает неверное её использование. Вообще. Другое дело, что это почти всегда недостижимый идеал, но это не значит, что теперь надо вообще отказаться от движения в эту сторону.
Пройдите по ссылке-то. Там printf, но его _нельзя вызвать неверно_. Даже в треде упоминаются варнинги GCC. Т.е. команда GCC признаёт опасность функции и так или иначе пытается это решить, а вы — нет.
Посмотрите, кстати, на типизированный printf из Cayenne (язык с зависимыми типами).
На пользователей, ага.
Если проводить аналогию с обычными магазинами, то их всё же заставляют давать какие-то гарантии (и вряд ли все согласятся, что это плохо, пусть и на это уходят деньги), и будь у меня выбор ходить в магазин, где всё дозволено, и магазин, который не имеет права ставить на полки просроченный продукт и продавать в продуктовом отделе ядовитые грибы, то я пойду во второй.
Насколько я понимаю, в обсуждаемом случае маркет один, и выбора нет.
Т.е. я считаю, что люди сами дураки, но не считаю, что не стоит менять политику. Маркет должен предоставлять товар надлежащего качества (кстати троян лежал в разделе «трояны»?), а ответственность должен нести магазин. Либо маркетов должно быть много.