Как жить без теории множеств

Nov 15, 2023 15:09

Я много раз наблюдал, как граждане, которым когда-то внушили, будто «можно только так - иначе математика останется без обоснований», недоумевали: а какая альтернатива-то? Что нам делать с числами разных сортов и свойств, если мы не считаем их элементами каких-то бесконечных множеств, состоящих друг с другом в мистических математических соотношениях?

Мне, как программисту со стажем, конечно, такое каждый раз было очень странно слышать. Тут ведь ничего даже придумывать не надо - оно придумано уже до нас и используется для практики так широко, как теории множеств даже не снилось.

Но, видимо, этим тайным знанием обладают только программисты, да и то не все, а остальные этот вариант никак не могут отыскать в тайных закромах интернетов, поэтому я всё-таки нашёл в себе силы поделиться со страждущими ответом на вопрос «а как ещё-то?».

Так вот, вы-таки удивитесь, однако практически любая программа обрабатывает некие данные. И даже если язык программирования делает вид, что типов у элементов, блоков и всех данных целиком в нём нет, на самом деле они всё равно есть - просто не всегда показываются программисту в явном виде.

Однако несколько проще рассматривать такой язык, где это всё-таки в явном виде указывается: какой тип у каждой хреновины, которую ты каким-то образом обрабатываешь в программе.

Прежде всего, что такое вообще «тип»? Ну или «класс»?

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

Кто-то подумает, что «тип» - это «имя множества, к которому принадлежит элемент», просто программисты ввиду своей тотальной безграмотности об этом не догадываются.

Но нет, ключевое отличие в том, что «множество» прямо по своему определению в «теории множеств» подразумевает, что у него существуют элементы. Причём все, которые в него входят, и сразу - в момент введения этого множества в рассмотрение.

Однако «тип» вообще ничего не предполагает про то, сколько элементов такого типа может существовать. В общем случае их - ноль или больше. Мы не знаем. И даже не пытаемся узнать, поскольку это вообще ни для чего не нужно. Оставьте подсчёт количества необнаружимых, но всё равно существующих вещей теологам.

Вместо этого «тип» определяет что-то вроде «контракта» для своих носителей: он гарантирует, что для них определены какие-то действия.

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

На практике это нужно для того, чтобы вовремя предупредить программиста о том, что его попытка нарезать ножом целое число - это какая-то ошибка. Ничем хорошим такое не кончится.

А точнее, оно просто кончится ничем, поскольку для целого числа такая операция не определена.

Ну, сейчас не определена. А так-то, если программист вдруг захочет нарезать ножом целые числа, то пусть такую операцию определит и нарезает.

Этот подход, разумеется, выходит далеко за пределы программирования и в одинаковой степени, хоть в физике, хоть в математике, хоть в музыке позволяет определять вот такие «контракты» для понятий. Мы можем транспонировать ноту, написать закон сохранения энергии для некоторого тела в определённых условиях или скалярно умножить два вектора друг на друга, если соответствующие действия для всего этого определены.

Я сейчас пишу «действия» исключительно ради общности, поскольку в разных языках они могут реализовываться разными способами. Например, действие может быть вписано внутрь определения «класса» и в этом случае называться «методом класса», а может быть описано где-то вовне в виде функции (в программистском смысле этого слова), однако суть остаётся всё той же: там, где мы пытаемся совершить некоторое действие над объектом какого-то типа, мы должны иметь возможность убедиться, что такое действие определено хоть каким-то способом.

Не все языки такое умеют проверять «заранее» - то есть ещё до запуска программы, однако на всех языках хоть в какой-то момент такая проверка будет сделана, и попытка совершить неопределённое для данного типа действие приведёт к результату с типом «ошибка».

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

Например, если у типа «человек» есть действие «взять предмет», то мы можем ввести ещё одно действие - «взять нож» на базе «взять предмет». Таким образом мы сузим количество возможных типов параметра у нового действия «взять», но зато будем уверены, что если мы какой-то предмет таким действием взяли, то это - предмет типа «нож», а значит для него определены все те действия, которые определены для ножа.

После этого мы можем вызвать действие «взять яблоко», действие «положить его на разделочную доску» и действие «нарезать при помощи ножа» (разумеется, все они тоже должны быть уже определены), и объединить всё это в новое действие - «нарезать яблоко ножом». У этого действия будут параметры «данное яблоко» и «данный нож», например.

Вполне понятно, что если для определения действий нужны другие действия, то что-то тут не так - откуда возьмутся первые действия-то?

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

Грубо говоря, эти первичные, «неопределяемые» действия проистекают из некоторых несомненных и объективных знаний об окружающем мире. Желающие, например, могут убедиться, что у процессора правда есть такая команда, струна гитары, сто пудов, может издавать звук, яблоки действительно существуют и так далее.

Однако, заметьте, даже в последнем случае, когда мы проверяли существование яблок, мы не проверяли и не предполагали сколько их вообще. Это нам не понадобилось. Более того, даже если единственное существовавшее в мире яблоко исчезнет, это не помешает нам определить тип «яблоко» и уверенно им пользоваться.

Поскольку тип, как уже говорилось, в общем случае не предполагает существование объектов с таким типом. Он говорит о том, что с этим объектом можно будет делать, если мы такой всё-таки обнаружим или создадим сами.

Мы могли бы даже сочинить тип чисто у себя в голове, придумав заодно некие произвольные «неопределяемые действия» для объектов такого типа. Другой вопрос, что в этом случае то, что мы дальше построим на базе этого типа, может лишь по чудесному стечению обстоятельств совпасть с хоть какими-то закономерностями реального мира. Однако сама возможность описания чисто воображаемых миров всё ещё остаётся.

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

Более того, мы даже не предполагали, что объекты с таким типом обладают ещё хоть какими-то свойствами, кроме того набора действий, которые можно с ними делать. Мы можем добавить эти свойства к конкретному типу - в виде действий вроде «получить цвет объекта» или «установить цвет объекта», но мы делаем это в явном виде, если у нас есть на то желание, а не вписываем это сразу в саму систему.

Что ещё интересно, понятие «идентичности» для таких объектов оказывается несколько сложнее, чем его пытаются представить в математике. Так, например, объекты могут быть разными в смысле того, что это - два разных экземпляра этого типа, но идентичными в смысле того, что определённое для них действие «сравнить друг с другом» будет говорить нам «они равны».

Как такое может быть?

Ну, написали вы число 1 на бумажке. А потом на другой бумажке тоже написали число 1. Эти числа равны «по значению», однако это не один и тот же объект - они ведь написаны на разных бумажках. При этом, если я и Вася указываем на первую бумажку пальцем, то хоть нас и двое, мы указываем не только на равные по значению объекты, но ещё и на один и тот же экземпляр.

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

Казалось бы, а зачем такое может понадобиться, например, в математике? Там-то «единицы» как бы всегда одни и те же ведь?

Ну. И в математике тоже мы, например, говорим «красный треугольник равен синему». Если равные объекты считались бы одним и тем же объектом, то как этот единственный треугольник мог оказаться одновременно красным и синим?

И вот тут в «теории множеств» имеется целая куча недосказанностей. Сделали мы, например, вот такую пару - (1, 1). И первый, и второй элементы пары - это элементы «множества целых чисел». А точнее, один и тот же элемент - там ведь может быть всего одна единица.

Однако в данной паре-то у нас что находится? Один элемент множества целых чисел и «копия элемента»? А который какой? Или там две «копии»? Но ведь термин «копия» у вас не определён.

Быть может, там две «ссылки» на элемент? Но ведь термин «ссылка» там тоже не определён, да и не выглядит оно ссылкой - написано ведь ровно то же, что в множестве целых чисел называлось просто «элементом».

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

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

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

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

Тем не менее, все эти добавки и уточнения тоже так и не привели к необходимости хоть как-то оговаривать «количество» экземпляров или же считать оные «уже существующими». Нет, всё ещё в общем случае их может существовать сколько угодно - в том числе, ноль штук. И это всё ещё вообще ничему не мешает.

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

Разумеется, в частных случаях мы можем указать и конкретное количество экземпляров. Например, сказать, что «вот у такого-то типа есть ровно два экземпляра, причём только вот такие» - большинство языков программирования в том или ином виде такое поддерживает.

Или мы можем сказать «у такого-то типа есть как минимум один, вот такой-то экземпляр» - это особенно полезно, если именно на этот экземпляр мы в явном виде ссылаемся в описании тех действий, которые можно совершать с объектами такого типа. Такое довольно часто делается, например, для пустого списка - все ссылки на пустые списки, таким образом, оказываются ссылкой на один и тот же экземпляр пустого списка, как бы мы оную ссылку ни получали - что созданием пустого списка, что удалением всех элементов из не пустого.

Кроме того, мы можем даже определить тип, у которого, самого по себе, не может быть ни одного экземпляра.

Зачем такое может быть нужно?

Ну, например, каков тип того, что хранится в пустом списке в том случае, когда любой пустой список - это ссылка на единственный экземпляр? Можно было бы сказать что-то вроде «тип у элементов тут самый базовый - “объект” или что там у вас в этой роли»?

Однако вот какая проблема. Был у нас, скажем, список собак, однако мы из него удалили всех собак. Это всё ещё список собак? Вроде бы да - тот, у кого этот список, наверняка всё ещё именно так про него и думает. Но ведь если у единственного экземпляра пустого списка типом его элементов считается «базовый объект», то это уже не может быть «список собак». Любая собака - объект, но не любой объект - собака.

Таким образом, в качестве типа нам нужно что-то прямо противоположное «базовому объекту» - не «предок всех типов», а «наследник всех типов». Какой-то тип, который является одновременно собакой, числом, философским изречением и вообще всем, что только можно придумать.

Если бы было возможно получить экземпляр такого типа, для него должны были бы быть определены абсолютно все действия, которые только можно определить, и все эти действия можно было бы с ним совершить. То есть экземпляр такого типа не может существовать в принципе. Но сам тип - почему бы и нет? Ни к каким противоречиям его существование не приведёт. Другое дело, что не только экземпляров, но и наследников у него быть не может - он ведь сам уже наследник абсолютно всех типов.

Правда, столь экзотичные варианты всё-таки относительно редки. Обычно же тип, у которого не может существовать ни одного экземпляра, подразумевает, что нельзя создать лишь экземпляр непосредственно самого этого типа. Но вполне можно создать экземпляр какого-то типа, который является его «наследником».

Например, не может быть создан экземпляр с типом «просто животное», хотя сам такой тип вполне себе имеет смысл. Нет, мы можем создать экземпляры «кошки», «какаду» или «ужа», каждый из которых - «животное», но никто из них не «просто животное и ничего больше».

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

В тех типах, экземпляры которых не могут создаваться сами по себе - без посредства типов-наследников, мы можем не писать, что именно происходит, когда с ними совершается некоторое действие - мы можем вместо этого сказать, что такое действие просто должно быть определено у каких-то типов-наследников, чтобы этим их «родительским типом» можно было пользоваться.

Например, в математике есть понятие «группа». Это такое множество, у которого определена операция условно «умножения». Условно - потому что эта операция не описана в виде конкретных действий, которые совершаются при её выполнении. Вместо этого только даны свойства, которым должен удовлетворять результат выполнения этой операции.

Например,

a * (b * c) == (a * b) * c

Кроме того, у группы должен существовать нейтральный элемент «e», который тоже дан не в явном виде, а лишь в виде его свойств.

e * a == a * e == a

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

Однако слишком многое приходится заметать под ковёр - ввиду того, что «множество» подразумевает существование всех своих элементов в уже определённом виде.

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

Каким образом возможно, например, оперировать в рассуждениях не «нейтральным элементом целых чисел - числом 1» (если под условным «умножением» понимается умножение целых чисел), а нейтральным элементом именно «группы», если она сама по себе не «множество», а значит и понятие «элемент» для неё не определено?

А если она всё-таки «множество», то «множество» чего именно? Какие его элементы «уже существуют, причём сразу все»?

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

С другой стороны, в излагаемой тут версии «из языков программирования» с этим нет никаких проблем и недосказанностей. Всё от начала и до конца можно совершенно однозначно запротоколировать.

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

Однако, поскольку про «заранее существующие элементы множеств» у нас речь не идёт, нас вовсе не беспокоит то, что на этом этапе никаких экземпляров элементов нет и быть не может. Мы заявили действие «умножение» и действие «получить нейтральный элемент» - и понеслась.

Когда кто-то захочет всем этим воспользоваться, то есть совершать действия над конкретными экземплярами, пусть он для своего типа, являющегося наследником нашего, эти два действия определит.

И пожалуйста - целые числа, рациональные числа, вещественные, комплексные, векторы, всё подходит. Если вы захотите, то можете перемножать даже строки или собак - придумайте только, каким для них будет действие «умножить», обладающее теми свойствами, которые требуются для «группы».

Ну и аналогично с «нейтральным элементом» - определите для вашего типа-наследника действие «получить нейтральный элемент» и вуаля.

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

И заметьте, мы снова ничего не предполагали про количество уже существующих экземпляров тех типов, экземпляры которых могут быть созданы. Только лишь ввели особый случай, при котором экземпляры непосредственно этого типа (а не какого-то из его наследников) не могут быть созданы вообще.

Иными словами, «прямо в концепцию» вписаны только два случая: «строго ноль экземпляров данного типа как финального» и «ноль или больше». Указания же конкретного их количества и конкретных, заранее заданных экземпляров всё ещё опциональны.

Если же вы не используете типы, где действия заявлены, то для вас в деле вообще только один случай из тех, что «прошиты прямо в концепции» - ноль или больше экземпляров.

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

А сколько программ написано на языках, базирующихся на теории множеств в версии Кантора или Цермело-Френкеля?

Я подскажу: ноль.

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

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

Чудом, не иначе - ведь без теории множеств никак. Беспросветная жизнь без основ, вот это вот всё.

doc-файл

наука, философия, программирование

Previous post Next post
Up