Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Новости и прочее

О чистоте и нечистоте функций и языков

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

Программа, целиком состоящая из чистых функций, не нужна. Доказательство.

Приведу на этот счёт рассуждения, которые можно воспринимать как математическое доказательство.

Допустим, необходимо написать программу только из чистых функций. В ней не может присутствовать вывод информации. Ведь подпрограммы вывода производят побочные эффекты, которым в чистых функциях не место. Если программе из чистых функций появятся результаты, то нет способа отдать их потребителю информации.

Далее руководствуемся принцип ленивости: если результаты не выводятся, то значит они не нужны, их можно убрать. Следовательно, функции, формирующие результаты, можно не только не вызывать, но и исключить из программы. Так же оказывается ненужным ввод информации, так как у этой информации нет потребителя в лице функций, работающих на результат, которые были исключены из программы шагом ранее. В итоге мы получаем, что

  • подпрограмм вывода нет,
  • результатов нет,
  • функций расчёта результатов нет,
  • подпрограмм ввода нет.
У нас получилась пустая программа. Которая, естественно, никому не нужна. Доказательство состоялось.

Но не нужна не только программа, целиком состоящая из чистых функций. Можно сделать и более общий вывод: язык, в котором есть только чистые функции, так же не нужен. Язык без ввода и вывода — такая же пустышка. Так что когда вы где-то читаете, что «Хаскелл позволяет программировать в императивном стиле», то помните, что это не Хаскелл позволяет, а реалии принуждают к императивному программированию. Это не высоколобые программисты на Хаскелле снизошли и дали послабление неокрепшим в функциональной вере. Это лишь признание того факта, что без императивного программирования невозможно существование функционального. Аминь.

Сочетание чистых и нечистых функций в языках

Итак, мы убедились, что языки без нечистых функций ни на что не способны. Поэтому, извлекая выгоды из применения чистых функций, разработчики вынужденно пользуются недертерминированными функциями и функциями с побочными эффектами. В языке D чистые функции выделены синтаксически: ключевое слово «pure» гарантирует, что внутри неё могут быть вызваны только функции с таким же ключевым словом. Эти чистые функции могут быть вызваны из нечистых, но не наоборот.

Надо заметить, что чем крупнее задачи решает подпрограмма, чем больше её функциональность, тем меньше у неё шансов остаться чистой. Потому что отказаться от ввода и вывода всё менее возможно. Этого требуют задачи, стоящие перед ней.

Стоит отметить, что язык препроцессора, например в языке Си, является надстройкой над обычным языком и этот отдельный язык является чистым. Поэтому Си, так же как и D, можно считать смешанным в разрезе чистоты и нечистоты. Или гибридным функционально-императивным. В дискуссиях о чистых языках программирования некоторые участники, доказывая нечистоту Хаскелла, приводят такой аргумент, что препроцессор Си — самый что ни на есть чистый язык, вот только почему на нём невозможно написать полноценную программу?

Чистые функциональные языки программирования

Выше мы доказали, что программа, целиком написанная на чистых функциях, никому не нужна, и, как следствие, языки, в которых возможны только чистые функции, тоже никому не нужны. Но чистые языки программирования, оказывается, существуют. Википедия относит к ним языки Clean и Haskell. А язык Clean даже название имеет говорящее, «чистый» в переводе с английского.

Давайте, сто пишут по этому поводу авторитеты. Читаем Мирана Липовачу, «Изучай Haskell во имя добра!», на стр. 17:

Язык Haskell – это чисто функциональный язык программирования. В чисто функциональных языках у функций отсутствуют побочные эффекты.

У любого человека, который мыслит логически, возникает когнитивный диссонанс: как чистые функции могут выполнять нечистые действия, если нечистота несовместима с чистотой? Ввод недетерминирован, вывод производит побочные эффекты. И то, и другое исключает чистоту функций. Но как? Как Haskell и Clean, будучи чистыми, делают нечистые вещи?

Нечистота в чистых языках

Именно эта противоречие вызывает недоумение, образует барьер, мешающий изучению функционального программирования. Объяснение чистоты функций, с помощью которых происходит ввод и вывод, обычно делается так. Мол, действия ввода/вывода оборачиваются в монаду IO и такая инкапсуляция сохраняет функциям чистоту. Очень похоже на то, как если бы травоядные оборачивали мясо в капусту (то есть инкапсулировали), и съедали, не умаляя стерильности своего вегетарианства.

Как чистые (как утверждается) функции, имеющие аргументом тип IO, инициируют ввод и вывод? Ведь они чисты и значит ленивы. А ленивые функции сами по себе не могут заработать. Их должен вызвать кто-то неленивый. Вопрос: кто вызывает чистую ленивую функцию с аргументом типа IO? Если будет ответ, что все функции типа IO будут в конечном счёте вызваны функцией main, то это тоже ничего не объясняет. Ведь функция main такая же, как и остальные. Она тоже имеет тип IO, она тоже ленива. Кто же тогда так вероломно вызывает функцию main – такую чистую и ленивую? Операционная система? Да как она посмела?! Она что, ослепла? Не видит что ли, что main чиста и ленива? Д. Шевченко, "О Хаскелл по-человечески", стр. 30:

Язык Haskell — ленивый. Это означает, что он никогда не сделает работу, результат которой никому не нужен.

Вывод может быть один: функция main вызывается операционной системой вопреки её чистоте.

Разоблачение магии

Фокус заключается в том, что все функции типа IO возвращают кортеж. Одним элементом кортежа является собственно результат. А вторым элементом являются указатели на нечистые подпрограммы, которые необходимо вызвать для выполнения ввода или вывода. То есть чистые функции идут в одном пакете с указанием, как необходимо выполнить нечистые действия. В итоге всё это стекается к main. А вот её вызывает ОС, запуская как чистые функции, так и нечистые подпрограммы.

Опубликовано: 2022.03.16, последняя правка: 2023.01.19    22:23

ОценитеОценки посетителей
   ██████████████ 1 (33.3%)
   ██████████████ 1 (33.3%)
   ▌ 0
   ██████████████ 1 (33.3%)

Отзывы

     2023/12/08 03:02, Comdiv          # 

Цитата отсюда:

Мысленным экспериментом без всякой связи с реализацией. Например: «Программа, целиком состоящая из чистых функций, не нужна»

Тут как раз непонимание того, как это реализовать, хотя в первую очередь страдает реализация в вашем сознании понятия чистого функционального языка. Я не претендую на идеальность, но предложу зарисовку идеи, как его понимать.
x -- вся память компьютера
{ x = f1(x, y) -- объявление новой х, значение которой - возврат чистой функции от предыдущей x
{ x = f2(x, y) -- y - это ввод, который не может быть получен из x
-- и так далее
...
}
}
Задача прагматического функционально чистого языка — предложить набор таких понятий, чтобы на них было удобно программировать без потребности засовывать все состояние компьютера в аргумент. Выходить за пределы функциональной чистоты для этого не обязательно. Насколько могу судить, многие разработчики с этим справились — вот вам и связь с реализацией. А то, что вы трактуете это как нарушение чистоты, так это в лучшем случае результат недоразумения. Если у вас особый взгляд и на понятие алгоритма, то ничего удивительного, что и на другие вещи смотрите по-своему. Беда только в том, что эта не та особость, которая даёт преимущества, а та, что надевает шоры.

     2023/12/17 22:30, Автор сайта          # 

предложу зарисовку

В том то и дело, что зарисовок и эскизов много, в отличие от точных и научных фактов. Рассмотрим пример. Если в программе на Хаскелле имеется функция
write_file :: IO ()
то вызывающая её функция тоже должна иметь тип IO ():
use_write_file :: IO ()
Из-за этого у нас и функция main тоже имеет тип IO (), поскольку в ней есть ввод/вывод:
main :: IO ()
Но функция, которая вызовет эту main, тоже должна иметь тип IO (). А вызывает функцию main операционная система. Ищу в WinAPI хотя бы одну функцию типа IO () и не нахожу её. То есть вызов main типа IO () невозможен. Тем не менее этот вызов происходит.

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

Я не видел, чтобы такие моменты растолковывались в зарисовках и эскизах. Но без точного описания быстро скатываемся к принятию «на веру», когда вместо объяснений и доказательств показывают «на пальцах». А это — переход от науки к её антиподу.

А то, что вы трактуете это как нарушение чистоты, так это в лучшем случае результат недоразумения. Если у вас особый взгляд и на понятие алгоритма, то ничего удивительного, что и на другие вещи смотрите по-своему. Беда только в том, что эта не та особость, которая даёт преимущества, а та, что надевает шоры.

Это Вы так видите. А другие люди высказывали другое мнение, отличное от Вас. И я дорожу их мнением.

     2023/12/18 16:40, veector          # 

Простите пожалуйста за глупый вопрос, а зачем тогда придумали такую классификацию как "чистота функции", какой прок пытались получить от этого?

     2023/12/19 06:52, Вежлиный Лис          # 

Параллелизация. Если разделить весь код на чистый и грязный (а обещают, что чистого в программе более 90 процентов), то можно проводить автоматизированную параллелизацию его чистой части. Очень актуально для процессоров с 32+ ядрами.

     2023/12/20 18:45, Автор сайта          # 

Всё так и есть. Прогресс процессоров вынужденно пошёл по пути увеличения числа ядер. Чистые функции помогают «лишние» ядра задействовать. Стоит отметить, что фунциональное программирование так же помогает повысить безошибочность ПО. Во-первых, с помощью доказательного программирования. Во-вторых, полным или частичным тестированием. В-третьих, дублируя функции, когда по очереди работают разные версии одной и той же функции, а потом из полученных результатов путём «голосования» выбирается правильный. Это собственное ноу-хау: «Надёжные программы из ненадёжных компонентов». Последний вариант тоже утилизирует «лишние» ядра.

Программы, целиком состоящие из чистых функций, действительно не нужны. Британские же учёные утверждают, что в Хаскелле всё функции чистые. И хотят, чтобы джентльменам верили на слово. Я же вижу логическое противоречие между утверждением о поголовной чистоте функций в Хаскелле и программами на нём, содержащими недетерминированность и побочные эффекты.

Добавить свой отзыв

Написать автору можно на электронную почту
mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Содержание

Каким должен быть язык программирования?

Анализ и критика

●  Устарел ли текст как форма представления программы

●  Русский язык и программирование

●  Многоязыковое программирование

Синтаксис языков программирования

Синтаксический сахар

●  Некоторые «вкусности» Алгол-68

●  «Двухмерный» синтаксис Python

●  Почему языки с синтаксисом Си популярнее языков с синтаксисом Паскаля?

●  Должна ли программа быть удобочитаемой?

●  Стиль языка программирования

●  Тексто-графическое представление программы

●●  Разделители

●●  Строки программы

●●  Слева направо или справа налево?

●  Комментарии

●●  Длинные комментарии

●●  Короткие комментарии

●●  Комментарии автоматической генерации документации

●●  Нерабочий код

●●  Помеченные комментарии

●  Нужны ли беззнаковые целые?

●  Шестнадцатиричные и двоичные константы

●  Условные операторы

●  Переключатель

●  Циклы

●●  Продолжение цикла и выход из него

●  Некошерный «goto»

●  Изменение приоритетов операций

●  Операции присвоения и проверки на равенство. Возможно ли одинаковое обозначение?

●  Так ли нужны операции «&&», «||» и «^^»?

●  Постфиксные инкремент и декремент

●  Почему в PHP для конкатенации строк используется «.»?

●  Указатели и ссылки в C++

●●  О неправомерном доступе к памяти через указатели

●  Обработка ошибок

●  Функциональное программирование

●●  Нечистые действия в чистых функциях

●●  О чистоте и нечистоте функций и языков

●●  Макросы — это чистые функции, исполняемые во время компиляции

●●  Хаскелл, детище британских учёных

●●  Измеряем замедление при вызове функций высших порядков

●●  C vs Haskell: сравнение скорости на простом примере

●●  Уникальность имён функций: за и против

●●  Каррирование: для чего и как

●●  О тестах, доказывающих отсутствие ошибок

●  Надёжные программы из ненадёжных компонентов

●●  О многократном резервировании функций

●  Использование памяти

●  Почему динамическое распределение памяти — это плохо

●  Как обеспечить возврат функциями объектов переменной длины?

●●  Типы переменного размера (dynamically sized types, DST) в языке Rust

●●  Массивы переменной длины в C/C++

●●  Размещение объектов в стеке, традиционный подход

●●  Размещение объектов переменной длины с использованием множества стеков

●●  Размещение объектов переменной длины с использованием двух стеков

●●  Реализация двухстековой модели размещения данных

●●  Двухстековая модель: тесты на скорость

●●  Изменение длины объекта в стеке во время исполнения

●●  Размещение объектов переменной длины с использованием одного стека

●  Можно ли забыть о «куче», если объекты переменной длины хранить в стеке

●  Безопасность и размещение объектов переменной длины в стеке

●  Массивы, структуры, типы, классы переменной длины

●  О хранении данных в стеке, вместо заключения

●  Реализация параметрического полиморфизма

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

Компьютерный юмор

Новости и прочее




Последние отзывы

2024/04/25 21:05 ••• Ttimofeyka
Энтузиасты-разработчики компиляторов и их проекты

2024/04/23 00:00 ••• alextretyak
Признаки устаревшего языка

2024/04/21 00:00 ••• alextretyak
Постфиксные инкремент и декремент

2024/04/20 21:28 ••• Бурановский дедушка
Русский язык и программирование

2024/04/07 15:33 ••• MihalNik
Все языки эквивалентны. Но некоторые из них эквивалентнее других

2024/04/01 23:39 ••• Бурановский дедушка
Новости и прочее

2024/04/01 23:32 ••• Бурановский дедушка
Русской операционной системой должна стать ReactOS

2024/03/22 20:41 ••• void
Раскрутка компилятора

2024/03/20 19:54 ••• kt
О многократном резервировании функций

2024/03/20 13:13 ••• Неслучайный читатель
Надёжные программы из ненадёжных компонентов

2024/03/07 14:16 ••• Неслучайный читатель
«Двухмерный» синтаксис Python

2024/03/03 16:49 ••• Автор сайта
О неправомерном доступе к памяти через указатели

2024/02/28 18:59 ••• Вежливый Лис
Про лебедей, раков и щук