Во-первых, теперь мой черёд говорить «ребус» :)
А во-вторых, там же наверняка не просто списки, я угадал?
Можно из этого всего выдрать маленький кусок в виде отдельной функции: foo = map g
Как это будет выглядеть?
Продакшн я не писал, но активно писал (и пишу) SublimeHaskell — плагин для Sublime, ну и вот на Хаскеле — быстрее намного и проще :)
Добавил один конструктор — компилятор укажет все места, где надо что-то поменять. Поменял сигнатуру — так же.
В Питоне с этим дело обстоит несколько иначе. Обкладывать даже такие вещи тестами? Ну так это увеличивает время обнаружения ошибки на порядок и повышает время на их исправление.
Так что такое сравнение в лоб (вида «писал что-то похожее на одном и другом») — это всего лишь один элемент, необходимый для оценки, вывода по нему делать нельзя, ибо в наличии разные инструменты; влияют разные стили написания и разный background у пишущих.
> попробуйте написать библиотеку для обработки изображений, в которой функции каждый раз возвращают новую картинку вместо модификации той, которую ей передали в качестве параметра.
А это далеко не общий случай, это очень частный случай. Возьмем, например, обработку списков. В цепочке обработок списка вида take n . filter f . map g и типа [a] -> [a] никаких списков не создаётся, к слову, и более того, даже обход будет — один, а не три (в ленивом языке), и обходить будет не весь список (в конце ж у нас take n); причём map g, например, может быть оформлено отдельной функцией, которая якобы принимает список и возвращает «новый» список.
Для того же самого в императивном языке придётся придумывать итераторы или что-то типа того.
При этом в чистом языке можно писать и грязно (в том числе с мутабельными буферами), если надо, и даже внутри чистой можно выделять куски, которые где-то под капотом пишут грязно, но снаружи — чистые. А вот как чистоту добавить в изначально грязный язык?
Ну «можно пренебречь» не значит, что он бесплатный, а какой стоимостью сыра можно пренебречь — зависит от задачи. Можно ж и обратно развернуть — мутабельность и прочие плюшки тоже не бесплатны — увеличивают «стоимость» разработки.
> Теория не может быть языком.
Почему? Вы с HoTT знакомы?
> a и b эквивалентны, так как ссылаются на одну и ту же математическую сущность, но не идентичны
Именно эквивалентны, а не равны? Что вы подразумеваете под эквивалентностью? Насколько я знаю, с позиции какой-либо теории типов, для эквивалентности типы должны совпадать, разве нет? А разные они потому, что это — переменные. Т.е. надо держать в уме эту разницу между значениями и переменными, это просто разные вещи.
Давайте лучше другую запись, и на Хаскеле:
a = 5
b = a
Здесь a и b — одно и то же. Т.е. внутри они вообще говоря могут ссылаться на разные участки памяти с одинаковым содержимым, но для рассуждений о поведении программы это не должно иметь и не имеет значения. У них один тип и одинаковое значение. Т.е. не надо держать в голове, что есть значения, а есть переменные, мы рассуждаем только о значениях. Т.е. нет никаких двух разных пятёрок, число 5 — это число 5.
do
a <- newMVar 5
b <- readMVar a >>= newMVar
А вот тут a и b имеют тип MVar Int, и вот они не идентичны (a == b ≡ False), но их значения — равны. Вот тут у нас и впрямь переменные, что и выражено в их типах.
Ну, всё-таки предположение «если код на языке компилируется» более общее, нежели «если он ещё и проходит проверку стат анализатором». Если я ставлю какой-то пакет, я могу быть уже уверен, что оно хотя бы должно компилироваться. Может, для других языков принято ещё и указывать, проверку какими статическими анализаторами они проходят, но я о таком не слышал.
> программирование — это не математика
Ну а вот homotopy type theory — математика, и язык программирования заодно. Почему для инженерии язык на основе математики хуже?
> Вы сами ответили на свой вопрос
Нет, я спросил. Какой смысл в, например, различении одного числа 5 и «другого»? То, что их кладут в переменные, а потом про вопросе «есть два числа 5, они разные?» говорят, что таки разные, по-моему, пример телеги впереди лошади.
Ну, я поясню. Есть два списка, из 5 и из 1000 элементов, для простоты — чисел от 1 и далее. Список шаффлится рандомно, потом по очереди читается до конца, копируя элементы в вывод. Потом снова. Понятно, что через хотя бы 1000 выведенных элементов мы обязательно встретим там 1 (для любого из списков). Вопрос, с какой вероятностью следующий прочитанный из вывода элемент — 1 для первого и для второго списков? Из какого списка надо читать, чтобы встречать 1 (неприятность) как можно реже?
Не совсем. При чтении хаскель-функции я уверен, что она ничего лишнего не меняет и никуда там не пишет. Причем она может, но в случае, если это отражено в ее типе. Это не ограничение, а расширение возможности языка — ибо есть возможность ограничивать или явно разрешать. Вы же не будете аналогичную возможность (через какие-либо аннотации) считать ограничением возможностей языка. Статический же анализатор надо еще запустить, а тут достаточно читать код, это проще.
А причем тут физическая реальность? Давайте лучше возьмем математику, вроде как неплохой инструмент при описании реальности. Вот две единичные матрицы — они одна или две разные?
Какой же это костыль, когда именно так и делается везде, просто везде такой костыль намертво приварен, который ещё и не всегда работает (ибо есть всякие value-типы).
> Супертип вполне можно рассматривать как АДТ его субтипов, разве нет?
Ну, есть весомые различия. Субтипы можно добавлять где-то отдельно, а АДТ надо по месту. В данном же контексте — два объекта одного АДТ — один тип, их сравнение — вполне нормальная операция. Т.е. и автор доволен, что нельзя сравнивать объекты разных типов, и мы, что можно сравнить велосипед и скутер.
> nullable — это и есть optional
Ну, да, речь об умолчании же. Что лучше — optional по умолчанию или специальным образом оговоренный?
В чистых языках (а, я так понимаю, речь о них, раз обсуждается «каждый раз создаём новый») IsBufferFilled не может никуда положить, ибо нет мутабельных переменных. Вообще говоря, они есть, конечно, но в таком случае тип у Buffer будет другой (отражающий тот факт, что он мутабелен), ну и тогда не надо будет прикрываться ширмой «каждый раз новый буфер».
В Clean для такого есть en.wikipedia.org/wiki/Uniqueness_type
В Хаскеле — ST и State, например.
Т.е. функции вида «взять буфер, вернуть новый» соединяем в одну большую такую функцию, а потом ей кормим буфер. Изнутри ему некуда «убежать», потому результат — один «новый» буфер. ST — если хотим таки работать с мутабельными переменными и массивами, но с гарантией, что итоговый результат — чистый. Можно комбинировать, разумеется, под капотом ST, чуть повыше — State.
Насколько это на самом деле оптимизируется, я врать не буду, не проверял.
Да, и так как в статье сравниваются разные языки с разным пониманием того, что считать ошибкой, то, по-моему, утверждение о том, что речь в ней «об отлавливании ошибок» — некорректно. Речь там не об отлавливании, а о том, что считать ошибкой, а что — нет, в контексте того, упростит ли это reasoning about the code.
> Когда вы отгружаете 10 одинаковых коробок, вы не можете отгрузить одну и сказать, что отгрузил все.
Этот пример просто хорошо ложится на ООП, но он как-то надуман. Если у вас реально разные одинаковые коробки, то припишите им какой-то ИД разный, и вот они уже разные в чистом ФП, и никакой протечки тут нет, просто в мейнстримовых языках у каждого объекта и так есть ИД — ссылка на объект, но хорошое ли это умолчание?
> Противоречит, автор предлагает кидать ошибку всегда при сравнении разных типов.
Я, если честно, не очень понял, почему скутер и велосипед в том примере — обязательно разные типы. Ну пусть это будет АДТ (Algebraic data type), например.
> Валидность — понятие относительное. null как раз и показывает, что поле не заполнено.
Для этого есть всякие Optional (он же Maybe), если это действительно нужно. Вопрос в том же, хорошо ли умолчание, когда null есть почти у всего?
> Если вы возьмёте две коробки и положите в них одинаковый набор игрушек, то они не станут одной и той же коробкой с игрушками — это будет две разные коробки с одинаковым содержимым. Различать или нет такие объекты зависит от задачи.
К слову, в языках, где нет мутабельности, различать их не имеет смысла, так что там такой вопрос не стоит. Дальнейшие же рассуждения про то, что эффективнее сравнивать ссылки — в немутабельном случае тоже применимы.
> при возвращении мне нужно лишь проверить, что мне вернули объект эквивалентный тому, что я выдал
Так это разве противоречит тому, что написано?
> После заполнения каждой графы, анкета ещё не становится полностью валидной
Если говорить об этом конкретном примере, то незаполненная, даже пустая, анкета — вполне валидна, она — незаполнена, это ж разное. Невалидное — это null в полях «имя», «фамилия» при наличии ещё и методов вида «ФИО()», который к ним обращается.
А во-вторых, там же наверняка не просто списки, я угадал?
Можно из этого всего выдрать маленький кусок в виде отдельной функции:
foo = map gКак это будет выглядеть?
Кому хочется орудовать состоянием в более сложных примерах, ну можно взять и State.
Я там выше упоминал
take n . filter f . map g, как это переписать на императивный?Добавил один конструктор — компилятор укажет все места, где надо что-то поменять. Поменял сигнатуру — так же.
В Питоне с этим дело обстоит несколько иначе. Обкладывать даже такие вещи тестами? Ну так это увеличивает время обнаружения ошибки на порядок и повышает время на их исправление.
Так что такое сравнение в лоб (вида «писал что-то похожее на одном и другом») — это всего лишь один элемент, необходимый для оценки, вывода по нему делать нельзя, ибо в наличии разные инструменты; влияют разные стили написания и разный background у пишущих.
> попробуйте написать библиотеку для обработки изображений, в которой функции каждый раз возвращают новую картинку вместо модификации той, которую ей передали в качестве параметра.
А это далеко не общий случай, это очень частный случай. Возьмем, например, обработку списков. В цепочке обработок списка вида
take n . filter f . map gи типа[a] -> [a]никаких списков не создаётся, к слову, и более того, даже обход будет — один, а не три (в ленивом языке), и обходить будет не весь список (в конце ж у нас take n); причём map g, например, может быть оформлено отдельной функцией, которая якобы принимает список и возвращает «новый» список.Для того же самого в императивном языке придётся придумывать итераторы или что-то типа того.
При этом в чистом языке можно писать и грязно (в том числе с мутабельными буферами), если надо, и даже внутри чистой можно выделять куски, которые где-то под капотом пишут грязно, но снаружи — чистые. А вот как чистоту добавить в изначально грязный язык?
Почему? Вы с HoTT знакомы?
> a и b эквивалентны, так как ссылаются на одну и ту же математическую сущность, но не идентичны
Именно эквивалентны, а не равны? Что вы подразумеваете под эквивалентностью? Насколько я знаю, с позиции какой-либо теории типов, для эквивалентности типы должны совпадать, разве нет? А разные они потому, что это — переменные. Т.е. надо держать в уме эту разницу между значениями и переменными, это просто разные вещи.
Давайте лучше другую запись, и на Хаскеле:
Здесь a и b — одно и то же. Т.е. внутри они вообще говоря могут ссылаться на разные участки памяти с одинаковым содержимым, но для рассуждений о поведении программы это не должно иметь и не имеет значения. У них один тип и одинаковое значение. Т.е. не надо держать в голове, что есть значения, а есть переменные, мы рассуждаем только о значениях. Т.е. нет никаких двух разных пятёрок, число 5 — это число 5.
А вот тут a и b имеют тип
MVar Int, и вот они не идентичны (a == b ≡ False), но их значения — равны. Вот тут у нас и впрямь переменные, что и выражено в их типах.Ну а вот homotopy type theory — математика, и язык программирования заодно. Почему для инженерии язык на основе математики хуже?
> Вы сами ответили на свой вопрос
Нет, я спросил. Какой смысл в, например, различении одного числа 5 и «другого»? То, что их кладут в переменные, а потом про вопросе «есть два числа 5, они разные?» говорят, что таки разные, по-моему, пример телеги впереди лошади.
> Супертип вполне можно рассматривать как АДТ его субтипов, разве нет?
Ну, есть весомые различия. Субтипы можно добавлять где-то отдельно, а АДТ надо по месту. В данном же контексте — два объекта одного АДТ — один тип, их сравнение — вполне нормальная операция. Т.е. и автор доволен, что нельзя сравнивать объекты разных типов, и мы, что можно сравнить велосипед и скутер.
> nullable — это и есть optional
Ну, да, речь об умолчании же. Что лучше — optional по умолчанию или специальным образом оговоренный?
В Clean для такого есть en.wikipedia.org/wiki/Uniqueness_type
В Хаскеле — ST и State, например.
Т.е. функции вида «взять буфер, вернуть новый» соединяем в одну большую такую функцию, а потом ей кормим буфер. Изнутри ему некуда «убежать», потому результат — один «новый» буфер. ST — если хотим таки работать с мутабельными переменными и массивами, но с гарантией, что итоговый результат — чистый. Можно комбинировать, разумеется, под капотом ST, чуть повыше — State.
Насколько это на самом деле оптимизируется, я врать не буду, не проверял.
Этот пример просто хорошо ложится на ООП, но он как-то надуман. Если у вас реально разные одинаковые коробки, то припишите им какой-то ИД разный, и вот они уже разные в чистом ФП, и никакой протечки тут нет, просто в мейнстримовых языках у каждого объекта и так есть ИД — ссылка на объект, но хорошое ли это умолчание?
> Противоречит, автор предлагает кидать ошибку всегда при сравнении разных типов.
Я, если честно, не очень понял, почему скутер и велосипед в том примере — обязательно разные типы. Ну пусть это будет АДТ (Algebraic data type), например.
> Валидность — понятие относительное. null как раз и показывает, что поле не заполнено.
Для этого есть всякие Optional (он же Maybe), если это действительно нужно. Вопрос в том же, хорошо ли умолчание, когда null есть почти у всего?
К слову, в языках, где нет мутабельности, различать их не имеет смысла, так что там такой вопрос не стоит. Дальнейшие же рассуждения про то, что эффективнее сравнивать ссылки — в немутабельном случае тоже применимы.
> при возвращении мне нужно лишь проверить, что мне вернули объект эквивалентный тому, что я выдал
Так это разве противоречит тому, что написано?
> После заполнения каждой графы, анкета ещё не становится полностью валидной
Если говорить об этом конкретном примере, то незаполненная, даже пустая, анкета — вполне валидна, она — незаполнена, это ж разное. Невалидное — это null в полях «имя», «фамилия» при наличии ещё и методов вида «ФИО()», который к ним обращается.