класс на языке ООП и тип

9

В теории языка программирования тип - это набор значений. Например, тип "int" - это набор всех целочисленных значений.

В языках ООП класс - это тип, не так ли?

Когда класс определен с более чем одним членом, например

class myclass{
    int a; 
    double b;
}

Когда мы говорим о классе, мы имеем в виду

  • « (a,b)где aint и bдвойник», или
  • "{ (x,y)| x- это любое int, yлюбое двойное}"?

Что означает экземпляр myclass?

  • « (a,b)где aint и bдвойник», или
  • объект, который занимает пространство памяти и который может (не обязательно, т. е. может быть пустым) хранить (x,y), где xнаходится какое-либо целое и yлюбое двойное число?
Тим
источник
2
Класс это тип. «{(x, y) | x - это любое целое число, y - любое двойное}» » было бы почти правильно, за исключением двух вещей: 1) вы использовали кортеж, а класс концептуально является записью - вы ссылаетесь на его поля по имени, а не по положению, и 2) Не каждая запись с полями aи bявляются членами этого типа, как упоминает Киллиан Форт. Myclass изоморфен записям с полями aи bтипа intи double- вы можете взять такую ​​запись и превратить ее в экземпляр myclass.
Довал
1
В строго типизированных языках класс является типом. В слабо типизированных языках это может быть или не быть типом.
shawnhcorey
1
В теории языка программирования тип - это набор значений? Я думаю, вам нужно найти себе другую книгу, другого учителя или обоих. «Переменная» или «константа» имеет «тип» и часто имеет «значение». Существуют типы с нулевым значением, скаляры и типы составного значения, в которых значение переменной или константы содержит суб-переменные / субконстанты.
user1703394
1
@ user1703394 Тип - это набор значений. 32-разрядный целочисленный тип представляет собой набор из 2 ^ 32 различных значений. Если выражение оценивается как значение этого типа, вы знаете, что значение находится в этом наборе. Математические операторы - это просто функции над значениями этого набора.
Довал
1
Я также был бы осторожен, рассматривая типы как наборы значений. Наборы имеют отношения, которые не являются строго применимыми к типам. Для концептуализации типов это хорошая модель, но она ломается, когда вы начинаете более внимательно смотреть на вещи - и тем более, когда вы вводите подтипы.
Теластин

Ответы:

30

Ни.

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

Например, если у вас есть класс a CartesianCoordinatesи PolarCordinatesкласс, они могут иметь два числа в качестве своих полей, и они могут даже иметь один Numberи тот же тип и одинаковые имена, но они все равно не будут совместимы, и экземпляр класса PolarCoordinatesне будет экземпляр CartesianCoordinates. Возможность разделять типы по назначению, а не по их текущей реализации, является очень полезной частью написания более безопасного и более удобного в обслуживании кода.

Килиан Фот
источник
9
Следует отметить , что в некоторых языках являются структурно эквивалентными является достаточно , чтобы сделать один тип подтип другого (и часто наоборот). Хотя это, безусловно, необычно / непопулярно.
Теластин
7
@Tim Typedef не создает тип, он псевдоним имени, используемого для ссылки на существующий тип.
Довал
1
@DevSolar Он явно упомянул C, и кроме C ++, я не знаю ни одного другого языка, который использует это ключевое слово.
Довал
3
@Telastyn - эти языки должны быть убиты огнем.
Джон Стори
4
@JonStory Структурный подтип полезен на уровне модуля; его отсутствие заставляет вас превращать все interfaceв Java и C #, если вы когда-нибудь захотите провести модульное тестирование. Вы заканчиваете тем, что пишете кучу шаблонов, чтобы вы могли изменить определенный класс, который будет использовать ваша программа, даже если вы не собираетесь менять его во время выполнения.
Довал
6

Типы не являются наборами.

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

Теория множеств позволяет вам создавать подмножества на лету , если у вас есть правило, которое описывает, что принадлежит подмножеству. Теория типов вообще не позволяет этого. В то время как большинство языков имеют Numberтип или что - то подобное, они не имеют EvenNumberтипа, и не было бы просто создать. Я имею в виду, достаточно просто определить сам тип, но любые существующие Numbers, которые оказываются даже, не будут волшебным образом преобразованы в EvenNumbers.

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

Наборы не могут прямо или косвенно содержать себя . Некоторые языки, такие как Python, предоставляют типы с менее регулярными структурами (в Python typeканонический тип есть typeи objectсчитается экземпляром object). С другой стороны, большинство языков не позволяют пользовательским типам участвовать в подобном обмане.

Наборы обычно допускают перекрытие, не будучи заключенными друг в друга. Это редко встречается в теории типов, хотя некоторые языки поддерживают ее в форме множественного наследования. Другие языки, такие как Java, допускают только ограниченную форму или полностью ее запрещают.

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

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


Понятие «подтип» действительно очень отличается от понятия «подмножество». Если Xэто подтип Y, это означает, что мы можем заменить экземпляры Yна экземпляры, Xи программа все равно будет «работать» в некотором смысле. Это скорее поведенческий, чем структурный характер, хотя некоторые языки (например, Go, Rust, возможно, C) выбрали последний из соображений удобства либо для программиста, либо для языковой реализации.

Kevin
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Мировой инженер
4

Алгебраические типы данных - способ обсудить это.

Существует три основных способа комбинирования типов:

  • Товар. Это в основном то, о чем вы думаете:

    struct IntXDouble{
      int a; 
      double b;
    }
    

    тип продукта; его значения - все возможные комбинации (то есть кортежи) одного intи одного double. Если вы рассматриваете типы чисел как наборы, то количество элементов типа продукта фактически является результатом количества элементов поля.

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

    data IntOrDouble = AnInt Int
                     | ADouble Double
    

    значения этого типа имеют либо форму AnInt 345, либо ADouble 4.23, но всегда используется только одно число (в отличие от типа продукта, где каждое значение имеет два числа). Итак, кардинальность: сначала вы перечисляете все Intзначения, каждое из которых должно быть объединено с AnIntконструктором. Плюс ко всему, все Doubleзначения в сочетании с ADouble. Отсюда тип суммы .

  • Возведение в степень 1 . Я не буду обсуждать это подробно здесь, потому что оно не имеет четкой ОО-корреспонденции вообще.

Так что насчет классов? Я сознательно использовал ключевое слово, structа не classдля IntXDouble. Дело в том, что класс как тип на самом деле не характеризуется своими полями, это просто детали реализации. Ключевым фактором является то, какие различимые значения могут иметь класс.

Что это отношение , хотя это, значение того или иного класса может быть значения любого из его подклассов ! Таким образом, класс на самом деле является типом суммы, а не типом продукта: если Aи Bоба будут производными myClass, то myClass, по сути, будет суммой Aи B. Независимо от фактической реализации.


1 Это функции (в математическом смысле! ); тип функции Int -> Doubleпредставлен экспоненциальным DoubleInt. Плохо, если у вашего языка нет правильных функций ...

leftaroundabout
источник
2
Извините, но я думаю, что это очень плохой ответ. Функции сделать имеют четкий OO аналог, а именно методы (и тип одного метода интерфейса). Основное определение объекта состоит в том, что он имеет как состояние (поля / члены-данные), так и поведение (методы / функции-члены); ваш ответ игнорирует последнее.
Руах
@ruakh: нет. Конечно, вы можете реализовать функции в ОО, но в целом методы не являются функциями ( потому что они изменяют состояние и т. Д.). В этом отношении «функции» в процедурных языках тоже не функционируют. Действительно, интерфейсы с одним статическим методом ближе всего подходят к функциям / экспоненциальным типам, но я надеялся избежать обсуждения этого вопроса, поскольку он не имеет отношения к этому вопросу.
оставил около
... что еще более важно, мой Anwer делает рассмотреть поведение. Действительно, поведение обычно является причиной, по которой вы используете наследование, и объединение различных возможных поведений точно отражает аспект типа суммы в классах ОО.
оставил около
@ruakh Метод не может без своего объекта. Ближайшим аналогом являются staticметоды, за исключением того, что они все еще не являются первоклассными значениями. Суть большинства языков OO в том, что они воспринимают объекты как наименьший строительный блок, поэтому, если вы хотите что-то меньшее, вы должны имитировать это объектами, и вы все равно в конечном итоге перетаскиваете кучу семантических функций, которых не должно быть. Например, не имеет смысла сравнивать функции на равенство, но вы все равно можете сравнивать два объекта с поддельными функциями.
Довал
@Doval 1) вы можете передавать методы AFAIK, чтобы они были первоклассными значениями; 2) имеет смысл сравнивать функции на равенство, JS люди делают это постоянно.
Ден
2

Извините, но я не знаю о "сырой" теории. Я могу предоставить только практический подход. Я надеюсь, что это приемлемо для программистов. Я не знаком с этикетом здесь.


Центральная тема ООП - сокрытие информации . Что именно представляют собой члены класса, должны быть неинтересны для его клиентов. Клиент отправляет сообщения (вызывает методы / функции-члены) экземпляра, которые могут изменить или не изменить внутреннее состояние. Идея состоит в том, что внутренняя часть класса может измениться, без влияния на клиента.

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

    int areacode;
    int number;

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

Построение экземпляра может отрицать любые отрицательные числа. Возможно, конструкция также каким-то образом нормализует ареакод или даже проверяет целое число. Таким образом , вы бы в конечном итоге гораздо ближе к вашему "(a,b) where a is an int and b is a double", потому что это , конечно , не какой - либо два ИНТ хранится в этом классе.

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

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


Существуют шаблоны проектирования, позволяющие гарантировать, что такие изменения внутреннего представления могут быть сделаны без ведома клиента (например, идиома pimpl в C ++, которая скрывает любые элементы данных за непрозрачным указателем ).

DevSolar
источник
1
It is neither the type of the data members, nor the range of their possible values that defines the class, it's the methods that are defined for it.Члены данных не определяют класс только тогда, когда вы их скрываете. Это может быть самый распространенный случай, но нельзя сказать, что это верно для всех классов. Если хотя бы одно поле является публичным, оно так же важно, как и его методы.
Довал
1
Если вы не программируете на Java, где у вас нет выбора в этом вопросе, и даже ваши глупые бездействующие записи должны быть class. (Маркировка их finalпомогает понять смысл, но все же). protectedТем не менее, у вас все еще есть проблема с членами, которые могут быть унаследованы и, таким образом, являются частью второго API для разработчиков подклассов.
Довал
1
@Doval: я понял, что это «теоретический» вопрос, поэтому я старался избегать актуальных языковых проблем. (Точно так же, как я остаюсь в стороне от Java и protectedнасколько это возможно на практике. ;-))
DevSolar
3
Проблема в том, что a classявляется зависимой от языка конструкцией. Насколько я знаю, classв теории типов такого понятия не существует .
Довал
1
@Doval: Разве это не значит, что теория типов сама по себе неприменима к классам, поскольку они представляют собой конструкцию, выходящую за рамки этой теории?
DevSolar
2
  • Типа является описанием категории / диапазон значений, составных структур, или что у вас. OOPwise, это похоже на «интерфейс». (В не зависящем от языка смысле. Не так сильно intзависит от языка. В Java, например, это тип , но он не имеет отношения к interface. Спецификации открытых / защищенных полей также не являются частью interface, но являются частью "интерфейса" или типа .)

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

  • Класс является реализацией типа. Это шаблон, который на самом деле определяет внутреннюю структуру, прикрепленное поведение и т. Д.

Chao
источник