LINUX.ORG.RU

Rust 1.12

 


1

6

Команда разработчиков Rust рада представить релиз Rust 1.12 — системного языка программирования, нацеленного на безопасную работу с памятью, скорость и параллельное выполнение кода. В этот релиз вошёл 1361 патч.

Новое в 1.12

По словам разработчиков, релиз 1.12 является, возможно, самым значительным с момента выпуска релиза 1.0. Самое заметное для пользователей изменение в версии 1.12 — это новый формат ошибок, выдаваемых rustc. Сообществом была проделана многочасовая работа по переводу вывода ошибок на новый формат. Кроме того, для лучшей интеграции и взаимодействия со средой разработки и другим инструментарием ошибки теперь можно выводить в формате JSON при помощи специального флага --error-format=json.

Самое большое внутреннее изменение — это переход на новый формат внутреннего представления программы MIR. Незаметное сегодня, это изменение открывает путь к череде будущих оптимизаций компилятора, и для некоторых кодовых баз оно уже показывает улучшения времени компиляции и уменьшение размера кода. Переход на MIR открывает ранее сложнодоступные возможности анализа и оптимизации. Первое из многочисленных грядущих изменений — переписывание прохода, генерирующего промежуточное представление LLVM, того, что rustc называет «трансляцией». После многомесячных усилий новый бэкенд, основанный на MIR, доказал, что готов к реальной работе. MIR содержит точную информацию о потоке управления программы, поэтому компилятор точно знает, перемещены типы или нет. Это значит, что он статически получает информацию о том, нужно ли выполнять деструктор значения. В случаях, когда значение может быть перемещено или не перемещено в конце области действия, компилятор просто использует флаг из одного бита на стеке, что, в свою очередь, проще для оптимизации проходов в LLVM. Конечный результат — уменьшенный объем работы компилятора и менее раздутый код во время исполнения.

Другие улучшения:

  • Множество мелких улучшений документации.
  • rustc теперь поддерживает три новые цели MUSL на платформе ARM: arm-unknown-linux-musleabi, arm-unknown-linux-musleabihf и armv7-unknown-linux-musleabihf. Эти цели поддерживают статически скомпонованные бинарные файлы. Однако, в собранном виде они пока не распространяются.
  • Повышена читабельность описаний ошибок в ссылках и неизвестных числовых типах.
  • Компилятор теперь может быть собран с LLVM 3.9.
  • Тестовые бинарные файлы теперь поддерживают аргумент --test-threads для указания количества потоков для запуска тестов, который действует точно так же, как переменная окружения RUST_TEST_THREADS.
  • В случае продолжительности выполнения тестов больше минуты показывается предупреждение.
  • Вместе с выпусками Rust теперь доступны пакеты с исходными кодами, которые можно установить при помощи rustup через команду % rustup component add rust-src. Исходные коды могут быть использованы для интеграции и взаимодействия со средой разработки и другим инструментарием.
  • Ускорено обновление индекса реестра.
  • cargo new получил флаг --lib.
  • Добавлен вывод профиля сборки (release/debug) после компиляции.
  • cargo publish получил флаг --dry-run.
  • Сокеты на Linux в подпроцессах теперь закрываются правильно через вызов SOCK_CLOEXEC.
  • Определения Unicode обновлены до 9.0.

Стабилизация библиотек:

  • Cell::as_ptr и RefCell::as_ptr.
  • IpAddr, Ivp4Addr и Ipv6Addr получили несколько новых методов.
  • LinkedList и VecDeque теперь имеют новый метод contains.
  • iter::Product и iter::Sum.
  • Option реализует From для содержащегося в нём типа.
  • Cell, RefCell и UnsafeCell реализует From для содержащихся в них типах.
  • Cow<str> реализует FromIterator для char, &str и String.
  • String реализует AddAssign.

Возможности Cargo

Самая большая возможность, добавленная в Cargo в этом цикле — «рабочие области». Описанные в RFC 1525, рабочие области позволяют группе пакетов разделять один общий файл Cargo.lock. Это позволяет намного легче придерживаться единственной версии общих зависимостей при наличии у вас проекта, который делится на несколько пакетов. Для включения этой возможности в большинстве мультипакетных проектов достаточно добавить одну единственную строчку [workspace] в Cargo.toml верхнего уровня, более сложным установкам может потребоваться дополнительная настройка.

Другая существенная возможность — переопределение источника пакета. При помощи инструментов cargo-vendor и cargo-local-registry можно переопределять зависимости локально (vendoring). Со временем это станет фундаментом для построения инфраструктуры зеркал crates.io.

>>> Подробный список изменений

>>> Подробности

★★★★★

Проверено: Falcon-peregrinus ()
Последнее исправление: Falcon-peregrinus (всего исправлений: 10)
Ответ на: комментарий от anonymous

Там не только наследования нормального нет, там и инкапсуляции нормальной нет. Это уже обсуждали.

Обязательно нужно тащить в язык всё и сразу, чтобы сделать второй C++, в котором есть всё, но вот только нихрена не работает без костылей.

Если бы оно не работало, я сильно сомневаюсь, что ты бы это здесь вообще написал)

asaw ★★★★★
()
Ответ на: комментарий от asaw

Так я и не писал что не работает, я писал что работает только с костылями. Отвечу тебе твоей же цитатой: «учи слова».

Там не только наследования нормального нет, там и инкапсуляции нормальной нет. Это уже обсуждали.

Что-то я пропустил. По учмолчанию все структуры и их поля в модуле приватные - это что такое?

Это, пожалуй, сойдет в качестве иллюстрации про костыли (из обсуждаемого выше).

Лучше рассказал бы про какое нибудь виртуальное наследование или деструкторы - крайне очевидные и ни разу не костыльные решения, и самое главное всегда предсказуемое поведение - зачем же вообще раст-то нужен? Недавно был тред где у человека std::move работал как std::swap - сразу видно, качественно сделано.

anonymous
()
Ответ на: комментарий от anonymous

Что-то я пропустил. По учмолчанию все структуры и их поля в модуле приватные - это что такое?

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

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

Действительно. А с деструкторами то что не так?

Недавно был тред где у человека std::move работал как std::swap - сразу видно, качественно сделано.

Это ничего не нарушает.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Действительно. А с деструкторами то что не так?

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

Это ничего не нарушает.

Только вот в одном из десяти случаев ещё как нарушает. Вот пишу я свою коллекцию, переместил где-то в объект и жду себе в каком-то алгоритме что его источник у меня будет пуст, а он внезано не пустой – и ведь такое даже наврятли упадёт. Вот и как поймать что-то подобное? Какой же всё таки некостыльный плюсы язык!

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

Эмм, почему бессмыслица-то? Тебе резко расхотелось сокрывать детали реализации друг от друга, или без наследования сакральный смысл сего паттерна резко обнуляется? Почему же трейты «прилеплены сбоку»? Потому что они сделаны максимально простыми и чистыми, дабы не уходить в порнографию с возрастной категорией «C++»?

anonymous
()
Ответ на: комментарий от anonymous

Почему же трейты «прилеплены сбоку»?

«Прилепляются сбоку». Реализуются не в классе (структуре), а в другом месте (модуле).

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

Вот пишу я свою коллекцию, переместил где-то в объект и жду себе в каком-то алгоритме что его источник у меня будет пуст,

Даже не знаю что это. Дислексия возможно.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Там не только наследования нормального нет, там и инкапсуляции нормальной нет. Это уже обсуждали.

У вас, мусью, синдром утёнка в терминальной стадии, вы требуете инкапсуляцию как в c++, и наследование как в c++, при том, что как раз в крестовой реализации эти 2 принципа сделаны через жопу и потому противоречат друг другу. В реальности всё ровно наоборот, в расте ООП (если под ООП подразумевать практичную, но ограниченную реализацию, как в c++) реализован правильно. Собственно все бест практики по ООП твердят «наследуйте интерфейс, а не реализацию», «расширяйте композицией, а не наследованием», относительно новые языки программирования по этому поводу придумали возможность запретить наследование от класса, концепцию защищённых полей и методов критикуют третий десяток лет, как нарушающую принцип инкапсуляции, но избавиться не могут, так как без подобных полуоткрытых потрохов ничего сделать невозможно. И вот появился раст. Первым делом добавили варианты, что позволило избавиться от «ООП» на дайнемик кастах. Вторым делом чётко разделили ООПшный указатель от конкретного. Третьим сделали человеческую инкапсуляцию, что решило все проблемы с доступом и лишними зависимостями. Внутри модуля видно всё, не нужно открывать потроха. Снаружи - только публичный интерфейс. Хочется расширить готовую чужую реализацию-кладите внутрь и перенаправляйте методы. Не очень удобно, зато без зависимостей от защищённых членов из чужого модуля и без неожиданно появляющихся методов, при обновлении библиотеки. Хочется, чтоб несколько реализаций внутри одной библиотеки делили часть данных и кода? Ну так, в модуле могут сидеть приватные функции и структуры, пихайте всё общее в них.

khrundel ★★★★
()
Ответ на: комментарий от anonymous

в одном из десяти случаев ещё как нарушает

переместил где-то в объект и жду себе

Нарушает ожидания анонимуса, вот же собака!

PS Не то чтобы плюсы были сделаны по-человечески, но именно с move через swap всё в порядке.

anonymous
()
Ответ на: комментарий от anonymous

именно с move через swap всё в порядке.

То, что moved-out объект может спокойно и безошибочно использоваться - это тоже «в порядке»?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

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

anonymous
()
Ответ на: комментарий от tailgunner

То, что moved-out объект может спокойно и безошибочно использоваться - это тоже «в порядке»?

В С++ это не moved-out, это цепляние бирки с надписью «делайте что хотите, мне оно больше не надо». Да, это костыль, а не механизм как в Rust, например, но смотреть на него надо именно так.

anonymous
()
Ответ на: комментарий от anonymous

именно с move через swap всё в порядке.

То, что moved-out объект может спокойно и безошибочно использоваться - это тоже «в порядке»?

То есть в целом нет

/0

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

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

И всё равно проблема тут не в swap, а в отсутствии статического контроля.

anonymous
()
Ответ на: комментарий от khrundel

У вас, мусью, синдром утёнка в терминальной стадии, вы требуете инкапсуляцию как в c++, и наследование как в c++, при том, что как раз в крестовой реализации эти 2 принципа сделаны через жопу и потому противоречат друг другу. В реальности всё ровно наоборот, в расте ООП (если под ООП подразумевать практичную, но ограниченную реализацию, как в c++) реализован правильно. Собственно все бест практики по ООП твердят «наследуйте интерфейс, а не реализацию», «расширяйте композицией, а не наследованием», относительно новые языки программирования по этому поводу придумали возможность запретить наследование от класса, концепцию защищённых полей и методов критикуют третий десяток лет, как нарушающую принцип инкапсуляции, но избавиться не могут, так как без подобных полуоткрытых потрохов ничего сделать невозможно. И вот появился раст. Первым делом добавили варианты, что позволило избавиться от «ООП» на дайнемик кастах. Вторым делом чётко разделили ООПшный указатель от конкретного. Третьим сделали человеческую инкапсуляцию, что решило все проблемы с доступом и лишними зависимостями.

Ты просто не понял о чем я пишу. Кроме того, ты ещё и фанбой. Инкапсуляция - это в первую очередь связь состояния с поведением, данных с функциями, которые ими манипулируют. В твоём же любимом расте «структура» - отдельно, интерфейсы (трейты) - отдельно, реализация трейтов и главное - ИХ СВЯЗЬ С ОБЪЕКТОМ - тоже отдельно! Пока весь код не перелопатишь, так и не поймешь что объект из себя представляет. Это практически ничем не отличается от голого Си. Марш читать про ООП, потом иди писать код. Когда напишешь на своём расте что-то посложнее Hello World - расскажешь.

asaw ★★★★★
()
Ответ на: комментарий от asaw

В твоём же любимом расте «структура» - отдельно, интерфейсы (трейты) - отдельно, реализация трейтов и главное - ИХ СВЯЗЬ С ОБЪЕКТОМ - тоже отдельно!

И это прекрасно.

Марш читать про ООП

Марш читать про low coupling.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Марш читать про low coupling.

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

asaw ★★★★★
()
Ответ на: комментарий от pftBest

Конечно же нет, раз в одном модуле может определяться более одного объекта. Или вообще ни одного. Модули в расте ничем особым не отличаются от модулей в других языках и они про другое.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Конечно же да, в определении инкапсуляции ничего не сказано сколько там должно быть обьектов. Это просто все привыкли что в C++/Java инкапсуляция реализована в виде классов, потому не могут понять языки Rust и Go в которых классов нет, и никакой инкапсуляции на уровне ниже модулей тоже нет.
Если тебе очень нужно инкапсулировать свой обьект, то для этого используют вложенные модули

mod a {
    mod b {
       struct B
    }

    mod c {
       struct C
    }
}

pftBest ★★★★
()
Последнее исправление: pftBest (всего исправлений: 1)
Ответ на: комментарий от pftBest

Это просто все привыкли что в C++/Java инкапсуляция реализована в виде классов, потому не могут понять языки Rust и Go в которых классов нет, и никакой инкапсуляции на уровне ниже модулей тоже нет.

... «не могут понять Си, где нет никакой инкапсуляции». И на уровне модулей это тоже не инкапсуляция.

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

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

asaw ★★★★★
()
Последнее исправление: asaw (всего исправлений: 1)
Ответ на: комментарий от tailgunner

О чем ты ведешь речь - догадываюсь, но здесь речь идет не про это.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Трейт Wings который ты прикрутишь сбоку не будет иметь доступа к данным, так что инкапсуляция не нарушится.

pftBest ★★★★
()
Ответ на: комментарий от pftBest

Трейт Wings который ты прикрутишь сбоку не будет иметь доступа к данным, так что инкапсуляция не нарушится.

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

asaw ★★★★★
()
Ответ на: комментарий от asaw

Да прочитай уже вот это: «инкапсуляция (encapsulation) - это механизм

«In programming languages, encapsulation is used to refer to one of two related but distinct notions, and sometimes to the combination[1][2] thereof:

A language mechanism for restricting direct access to some of the object's components
A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.»

В Rust есть и то, и другое. А ты просто странный.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от asaw

Rust provides a powerful module system that can be used to hierarchically split code in logical units (modules), and manage visibility (public/private) between them.

A module is a collection of items: functions, structs, traits, impl blocks, and even other modules.

Structs have an extra level of visibility with their fields. The visibility defaults to private, and can be overridden with the pub modifier. This visibility only matters when a struct is accessed from outside the module where it is defined, and has the goal of hiding information (encapsulation).

mod my {
    // A public struct with a public field of generic type `T`
    pub struct WhiteBox<T> {
        pub contents: T,
    }

    // A public struct with a private field of generic type `T`
    #[allow(dead_code)]
    pub struct BlackBox<T> {
        contents: T,
    }

    impl<T> BlackBox<T> {
        // A public constructor method
        pub fn new(contents: T) -> BlackBox<T> {
            BlackBox {
                contents: contents,
            }
        }
    }
}
impowski
()
Ответ на: комментарий от asaw

Это в расте есть. Если объект реализует какой-то еще трейт, которого нету в области видимости, то это тоже самое что объект его не реализует. Какая вообще разница что улитка может еще и летать, если в данном модуле она этого точно не сделает?

anonymous
()
Ответ на: комментарий от tailgunner

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

asaw ★★★★★
()
Ответ на: комментарий от anonymous

Это в расте есть. Если объект реализует какой-то еще трейт, которого нету в области видимости, то это тоже самое что объект его не реализует. Какая вообще разница что улитка может еще и летать, если в данном модуле она этого точно не сделает?

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

asaw ★★★★★
()
Последнее исправление: asaw (всего исправлений: 1)
Ответ на: комментарий от asaw

Если в расте есть и то, и другое

«Если»? Оно просто есть.

расскажи мне по какой причине, кроме как по причине отсутствия инкапсуляции,

Видишь ли, я не могу рассказать, что происходит из-за отсутствия инкапсуляции в Rust, потому что там есть инкапсуляция.

в расте нету наследования поведения.

Давай сначала ты расскажешь, почему оно необходимо.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от tailgunner

«Если»? Оно просто есть.

Оно есть с натяжкой в той размытой формулировке, которую ты процитировал из википедии (очевидно, её поправл какой-нибудь фанбой раста). Обычно формулировка более конкретная: «encapsulation is an Object Oriented Programming concept that binds together the data and functions that manipulate the data». И вот этого в расте нет.

Давай сначала ты расскажешь, почему оно необходимо.

Потому что это идин из основных принципов ООП.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Обычно формулировка более конкретная: «encapsulation is an Object Oriented Programming concept that binds together the data and functions that manipulate the data»

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

Давай сначала ты расскажешь, почему оно необходимо.

Потому что это идин из основных принципов ООП.

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

tailgunner ★★★★★
()
Ответ на: комментарий от impowski

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

asaw ★★★★★
()
Ответ на: комментарий от tailgunner

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

Это совершенно стандартная формулировка. Погугли хоть на русском, хоть на английском и убедись.

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

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

asaw ★★★★★
()
Ответ на: комментарий от tailgunner

в расте нету наследования поведения.

Давай сначала ты расскажешь, почему оно необходимо.

Ну в рамках ООП наследование решает как минимум две конкретные проблемы: реализация абстракцый (когда мы через наследование реализуем абстрактный интерфейс/класс). Code reuse - когда мы выделеям общее поведение для некоторых классов в базовай класс, потом используем этот базовый код при наследовании в классах верхнеге уровня (например в BaseSocket могут быть рализованы методы close/bind/read/write и далее этот код можно использовать как в UDP так и в TCP сокетах по средствам наследования).

С абстакциями в расту все болие / мение понятно там есть трейты (хотя не совсем ясно можно ли к базовой абстракции прицепить некоторые данные как например файловый дескриптор к BaseSocket). А вот как реализован code-reuse в RUST без наследования ?

PS. Я ничего никому не пытаюсь доказать. На RUST не писал (пока нет времени) но к языку присматриваюсь и интерисуюсь. Если ответите с примерами буду благодарен.

zaz ★★★★
()
Ответ на: комментарий от zaz

Опять же, там все висит в RFC, вероятно в будущих релизах сделают.

impowski
()
Ответ на: комментарий от asaw

Погугли хоть на русском, хоть на английском и убедись.

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

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

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

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

А как насчет мелочей вроде срока разработки и затрат на нее - ты их посчитал? Если да, то как?

Раст на примере Servo с этой задачей пока что справляется не многим лучше Си

Потому что 15 лет назад KHTML правильно отображал русские буквы, а какая-то бета Servo - нет. Ну окей.

tailgunner ★★★★★
()
Ответ на: комментарий от zaz

Ну в рамках ООП наследование решает как минимум две конкретные проблемы: реализация абстракцый (когда мы через наследование реализуем абстрактный интерфейс/класс)

Это не в ООП - это следствие (убогого) дизайна Си++ (в котором нет понятия интерфейса или сигнатуры). В Rust интерфейсы реализуются без наследования.

Code reuse - когда мы выделеям общее поведение для некоторых классов в базовай класс, потом используем этот базовый код при наследовании в классах верхнеге уровня

Code reuse вполне реализуется без наследования реализации (composition over inheritance). да и вообще привязывание наследования и reuse неестественно.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

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

Детский сад...

А как насчет мелочей вроде срока разработки и затрат на нее - ты их посчитал? Если да, то как?

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

asaw ★★★★★
()
Ответ на: комментарий от asaw

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

Детский сад...

Веско.

А как насчет мелочей вроде срока разработки и затрат на нее - ты их посчитал? Если да, то как?

По опыту они в ОО-яыках значительно меньше

То есть ты не считал.

tailgunner ★★★★★
()
Ответ на: комментарий от asaw

То есть все думали, что объект типа «улитка» умеет только ползать, а она вдруг раз - и полетела, потому что какой-то шутник прилепил к ней трейт Wings.

А все не должны думать об улитках. Все должны думать о чём-то что умеет ползать, а умеет ли это летать или нет им должно быть всё равно.

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

Когда ты уже начнёшь думать в терминах интерфейса, а не реализации?

loyd
()
Ответ на: комментарий от tailgunner
Code reuse вполне реализуется без наследования реализации (composition over inheritance). да и вообще привязывание наследования и reuse неестественно.

Ок я не спорю. Но вот к примеру я хочу в RUST реализовать свои сокеты базируясь на сисколах не используя стандартных библиотек. У меня мозг напроч испорчен ООП, и в рамках ООП я бы сделал вот так:

Socket (implement read/write/close/bind)
UDPSocket inherit Socket + implement connect/sendto/recfrom
TCPSocket ingerit Socket + implement connect/listen/accept

Для меня важно что для UDP и TCP сокетов методы read/write/bind/close имеют одинаковую реализацию и соотвецтвенно я их реализую только один раз в базовом классе, а UDP/TCP сокеты просто унаследовал от базавого.

Как принято решать такие проблемы в Rust ? Имеет ли смысл делать трейт UDPSocket и TCPSocket ? Или нужно на подобие С создать модуль Net в нем объявить структуру Soсket и методы read/write/bind/close + udp_connect/sendto/recfrom + tcp_connect/listen/accept + create_udp_socket + create_tcp_socket ? Или все смешать (общий функционал как функции модуля а конкретные сокеты как трейты) ? Можно пример (без реализаций просто набор деклараций) ? буду признателен.

zaz ★★★★
()
Ответ на: комментарий от loyd

А все не должны думать об улитках. Все должны думать о чём-то что умеет ползать, а умеет ли это летать или нет им должно быть всё равно.

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

Когда ты уже начнёшь думать в терминах интерфейса, а не реализации?

А кто тебе сказал, что я думаю по-другому? В том-то и проблема, что в расте у типа (структуры) нет интерфейса: тип сам по себе, а интерфейс сам по себе.

asaw ★★★★★
()
Ответ на: комментарий от zaz

Как принято решать такие проблемы в Rust ?

Статью по линку, которую тебе уже дали, прочитал?

Для меня важно что для UDP и TCP сокетов методы read/write/bind/close имеют одинаковую реализацию и соотвецтвенно я их реализую только один раз в базовом классе, а UDP/TCP сокеты просто унаследовал от базавого.

read/write/bind/close реализуешь вообще не ты. Они - часть некоего трейта, называемого Unix syscall API.

Имеет ли смысл делать трейт UDPSocket и TCPSocket ?

Почему тебе не посмотреть реализацию stdlib? https://doc.rust-lang.org/std/net/

Но они спроектированы совсем не так, как ты думаешь.

tailgunner ★★★★★
()
Ответ на: комментарий от asaw

ты не понимаешь, что ООП и есть метод декомпозиции

А ты правда считаешь, что иерархической декомпозиции не было до ООП?

tailgunner ★★★★★
()
Ответ на: комментарий от asaw

И вот этого в расте нет.

А это что:

struct A { u: u32 }

impl A {
  fn bar(&self) -> u32 { self.u + 3 }
}

impl Lol for A {
  fn foo(&self) -> u32 { self.u + 4 }
}

Данные и функции вместе? Вместе. А то, что этот Lol можно было определить в месте определения (на основе только публичного интерфейса) это доп. фича и никак твоё определение не опровергает.

loyd
()
Ответ на: комментарий от asaw

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

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

В том-то и проблема, что в расте у типа (структуры) нет интерфейса: тип сам по себе, а интерфейс сам по себе.

Как бы нет. У самого типа могут быть методы (думать об этом можно как о реализации одноимённого типажа). Но речь даже не о том. Сам интерфейс это не просто один какой-то типаж, это вся совокупность реализованных типажей, именно это и есть интерфейс. И они реализованы не абстрактно, а именно для данного типа и имеют доступ к полям.

loyd
()
Ответ на: комментарий от loyd

Так а интерфейс то у А какой? Чтобы это понять я должен пройтись по всем его impl не так ли? А impl означает implementation, то есть реализацию. Отсюда возвращаем тебе твой же вопрос:

Когда ты уже начнёшь думать в терминах интерфейса, а не реализации?

asaw ★★★★★
()
Ответ на: комментарий от asaw

Ну а как ещё если ты не понимаешь, что ООП и есть метод декомпозиции?

А какое там основное правило структурной парадигмы?

loyd
()
Ответ на: комментарий от loyd

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

Да-да, придумали огромный костыль: https://github.com/rust-lang/rfcs/pull/1023 А всё потому, что инкапсуляции данных нет.

asaw ★★★★★
()
Ответ на: комментарий от loyd

А какое там основное правило структурной парадигмы?

Стркутурная парадигма - это структурная парадигма, а ООП - это ООП. Это разные подходы.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Так а интерфейс то у А какой? Чтобы это понять я должен пройтись по всем его impl не так ли?

Достаточно посмотреть impl A {..}, во всех остальных случаях интересовать должно не «а какой интерфейс у A», а «реализует ли A такой-то интерфейс», а где это искать зависит от того какой типаж. Если твой, то у тебя, если не твой, то в библиотеке, которая предоставляет A.

Соб-но, так документация и строится: https://docs.rs/, выбирай любой для примера.

loyd
()
Ответ на: комментарий от asaw

Причём тут инкапсуляция-то?

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

В крестах этот обобщённый «ToString для Display» в принципе невозможно сделать (костыль через множественное наследование 100500 «классов» мы не рассматриваем). Вот и профукалось ваше ООП.

loyd
()
Ответ на: комментарий от loyd

Достаточно посмотреть impl A {..}, во всех остальных случаях интересовать должно не «а какой интерфейс у A», а «реализует ли A такой-то интерфейс», а где это искать зависит от того какой типаж. Если твой, то у тебя, если не твой, то в библиотеке, которая предоставляет A.

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

asaw ★★★★★
()
Ответ на: комментарий от loyd

Причём тут инкапсуляция-то?

При том, что именно инкапсуляция связывает состояние объекта с поведением - данные с интерфейсом.

В крестах этот обобщённый «ToString для Display» в принципе невозможно сделать (костыль через множественное наследование 100500 «классов» мы не рассматриваем). Вот и профукалось ваше ООП.

Ну да, если выкинуть из языка именно ту фичу, с помощью которой это и реализуется (предварительно высказав про неё своё субъективное мнение и обозвав костылём), то можно сказать, что невозможно (хотя там ещё CRTP остаётся для реализации концепций типа mix-in). А так ещё Скотт Майерс 15 лет назад описывал как такие вещи делать.

asaw ★★★★★
()
Ответ на: комментарий от loyd

Дык, а кто тут утверждает что ООП и есть декомпозиция?

ООП - один метод декомпозиции. Структурная парадигма - другой.

asaw ★★★★★
()
Ответ на: комментарий от tailgunner

[quota] Почему тебе не посмотреть реализацию stdlib? https://doc.rust-lang.org/std/net/ [/quota]

Посмотрел, в принципе та проблема которая меня интерисует может быть решена по средством композиций трейтов с дефолтной реализацией:

trait BaseSocket {
   fn get_fd(&self) -> i32;
   fn close(&mut self) {
      do_sys_call_socket_close( self.get_fd() );
   }
}

pub struct UDPSocket {fd:i32};
impl UDPScoket {
  fn send_to(&mut self, &addr) {
    ......
  }
}

impl BaseSocket for UDPSocket {
   fn get_fd(&self) -> i32 { self.fd }
}

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

zaz ★★★★
()
Ответ на: комментарий от zaz

Но както выглядит не очень

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

tailgunner ★★★★★
()
Ответ на: комментарий от asaw

То, что ты об интерфейсе узнаешь не из строчки class A : public B, а из строчек impl Foo for A абсолютно ничего не меняет.

А можно мне твоё видение impl<T> ToString for T where T: Display + ?Sized в Ъ-ООП стиле?

loyd
()
Ответ на: комментарий от tailgunner

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

В смысле ? Я чтото не так понял или в том плане что свои сокеты велосипедить никому не нужно ?

zaz ★★★★
()
Ответ на: комментарий от asaw

(предварительно высказав про неё своё субъективное мнение и обозвав костылём)

(за исключением упомянутого выше костыля)

Какие двойные стандарты.

loyd
()
Ответ на: комментарий от zaz

Ненужная - значит, что ты пытаешься абстрагировать какие-то ненужные вещи. Если делаешь библиотеку сокетов, то хотя бы не занимайся банальщиной вроде «абстракции read/write/close», а попытайся абстрагировать что-то полезное вроже «байтовый поток vs дискретные сообщения» (как сделано в Rust).

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Я не делаю библиотеку сокетов. Я пытаюсь расмотреть конкретную проблему на конкретном примере. А именно общую кодовую базу при реализации двух различных сущностей не более того ...

zaz ★★★★
()
Ответ на: комментарий от asaw

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

Вообще-то классическое определение - это изоляция внутренней реализации от интерфейса, и сокрытие подробностей реализации. Ну ОК, давайте возьмём ваше определение. Итак, в чем отличие крестового класса от растовой структуры? Не касаясь различия в АБИ, и там и там есть данные и методы. На первый взгляд, различие в том, что в цпп в объявлении класса перечислены также методы, что, конечно, никак не обеспечивает более плотную связь, но, по идее, делает поиск интерфейса класса чуть проще. Но только на первый взгляд: и IDE (даже те, что есть сейчас), и автоматически сгенерированная документация в расте отлично покажет и трейты и методы структуры, с другой стороны из-за наследования в объявлении крестового класса объявлены не все методы и данные, а из-за необходимости объявлять также и приватные методы и дружественные функции и классы, объявление класса настолько захламлено, что никакой особой ясности там нет. В итоге и там и там «связь» класса и методов находится либо через подсказку IDE, либо через документацию. С другой стороны, реализация интерфейсов «сбоку» имеет свои плюсы именно с точки зрения ООП. Согласно принципам ОО дизайна, клиентский код должен зависеть от абстрактного интерфейса, а не конкретных реализаций. Что означает необходимость включения в интерфейс всех частей алгоритма, которые могут варьировать я от реализации к реализации. С другой стороны эти же принципы требуют не перегружать интерфейс, да и не факт, что интерфейс и конкретные классы доступны для модификации. Чтоб решить этот конфликт, нужно городить копию дерева типов из адаптеров. В расте адаптеры не потребуются, просто свой трейт и реализации для конкретных типов.

khrundel ★★★★
()
Ответ на: комментарий от loyd

То, что ты об интерфейсе узнаешь не из строчки class A : public B, а из строчек impl Foo for A абсолютно ничего не меняет.

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

А можно мне твоё видение impl<T> ToString for T where T: Display + ?Sized в Ъ-ООП стиле?

Если тебе нужна не-ООП фича, то тебя никто не заставляет использовать ООП, можешь просто использовать обобщенные функции со специализациями/частичными специализациями - C++ это может, если ты про это. Если, всё же, хочется именно ООП, то есть известный паттерн на эту тему: https://en.wikipedia.org/wiki/Visitor_pattern

asaw ★★★★★
()
Ответ на: комментарий от asaw

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

Во-первых, в тех же c++ такое тоже возможно. class Mysnall: public Snall, public Wings { и привет, код, который не создаёт объект сам (а с фабриками никто не создаёт объект сам) не может быть уверенным в свойствах. Более того, даже интерфейс Wings не нужен, просто наследовался и переопределил какой-нибудь CanFly и привет. В этом как раз и проблема наследования реализаций. Во-вторых, в расте навешивание трейтов ограничено. Навесить можно любой трейт на свой тип, либо свой трейт на любой тип. Если тип улитка или трейт крылья определены тобой, тебе лучше знать, умеет ли улитка летать. Если кому-то нужно отправить улитку на другой конец Земли, он может реализовать для неё свой трейт FlyByPlane, реализовать посадку улитки в самолёт посредством её стандартных движений.

khrundel ★★★★
()
Ответ на: комментарий от asaw

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

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Во-первых, в тех же c++ такое тоже возможно. class Mysnall: public Snall, public Wings {

Нет, тут смысл в том, что в C++ ты для этого создаешь НОВЫЙ ТИП, а в расте ты можешь волшебным образом добавить новое поведение старому.

asaw ★★★★★
()
Ответ на: комментарий от asaw

в расте ты можешь волшебным образом добавить новое поведение старому.

Ровно таким же способом можно добавить новое поведение и в Си++ .

class Flyable {
  Snail *snail_;
public:
   void fly() { snail_->crawl(); }
}
tailgunner ★★★★★
()
Ответ на: комментарий от asaw

С каких это пор добавление нового поведения нарушает инкапсуляцию данных? Может ты еще скажешь что в C# нет инкапсуляции, потому что там есть extension методы?

pftBest ★★★★
()
Ответ на: комментарий от asaw

Нет, тут смысл в том, что в C++ ты для этого создаешь НОВЫЙ ТИП, а в расте ты можешь волшебным образом добавить новое поведение старому.

Как я уже писал, в мире IOC и фабрик ты не работаешь с типами, ты работаешь с объектами этого или наследуемого типа, которые, весьма вероятно, создаёшь не сам. Ну вот у меня есть код

void PetCollection::AdoptPet(Snail*newPet)
{
    m_Fence.Add(newPet); // помещаем за оградку, так как не умеет прыгать или летать
}
И тут приходит WingedSnail и всё падает. Почему? Потому что кривая C++/С#/Java поддержка ООП позволяют наследование реализаций и не разделяют указатель на конкретный тип и указатель на базовый тип для ОО. В расте так не получится, если я принимаю улиток, то получу я улитку. Если принимаю животное, то должен ожидать, что животное может оказаться любым.

khrundel ★★★★
()
Ответ на: комментарий от asaw

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

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

Но вместо этого начинается — ООП кривой, наследование реализации — кривизна, ООП в C++/C#/Java — кривое и т.д., и т.п. А вот в Rust-е ООП — такое как надо ООП. Настоящее.

eao197 ★★★★★
()
Последнее исправление: eao197 (всего исправлений: 2)
Ответ на: комментарий от eao197

Вашему терпению можно позавидовать.

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

Только представьте: кто-то может реализовать метод reproduce(e: Environment) для структуры Virus, хотя это структура не унаследована от структуры Animal. Ужасно. Если человек на такое решился, то у него уже нет никаких моральных ограничений. Я даже не хочу думать, что он там может понаписать. /s

red75prim ★★★
()
Ответ на: комментарий от asaw

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

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

Интереснее то, что наследование конфликтует с твоим определением инкапсуляции, потому что поведение задаётся не здесь, а в базовом классе.

loyd
()
Ответ на: комментарий от red75prim

Действительно, не понимаю. ООП — это устоявшаяся за 30+ лет троица: инкапсуляция, наследование, полиморфизм. Если это все в языке X из коробки — значит язык X можно назвать ООЯ. Нет — значит нельзя.

Не более того.

Если язык не ООЯ, это вовсе не значит, что он плох и что на нем разрабатывать софт сложнее, чем на ООЯ. Это просто значит, что язык не ООЯ. Посему называть его ООЯ или говорить, что он поддерживает ООП просто не правильно.

eao197 ★★★★★
()
Ответ на: комментарий от asaw

в расте ты можешь волшебным образом добавить новое поведение старому.

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

ты для этого создаешь НОВЫЙ ТИП

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

loyd
()
Ответ на: комментарий от asaw

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

Реализация - это то, что в фигурных скобочках. А интерфейс - это название функции и её сигнатура (та штука, которую в C/C++ копипастят в/из заголовочные(х) файлы(ов)). В Расте, созданием человекочитаемой документации на интерфейс занимается генератор документации.

red75prim ★★★
()
Ответ на: комментарий от eao197

Эй нет, вряд ли здесь какой-то rustоман будет утверждать, что там есть ООП (как минимум потому что под ООП сегодня понимается всё что угодно, поэтому и говорить об этом смысла чуть более, чем нисколько). Но тут приходит человек и начинает оперировать отдельными понятиями (считая их каким-то особым свойством его недоязычков), которые, как внезапно выясняется, выполняются и в rust-е.

loyd
()
Ответ на: комментарий от eao197

Это просто значит, что язык не ООЯ.

Спор в основном идет не о классификации Раста. asaw утверждает, что язык без наследования реализаций - не язык, а ходячее недоразумение, или что-то в этом роде.

red75prim ★★★
()
Ответ на: комментарий от red75prim

Мне показалось, что речь о другом. asaw говорит, что если в языке нет инкапсуляции и наследования (реализаций), то язык не поддерживает ООП, а это не есть хорошо, т.к. ООП уже доказало свою состоятельность и т.д. и т.п.

На что ему говорят, что в С++/C#/Java — кривое ООП, а вот в Rust-е то с ООП все нормально как раз из-за того, что в языке нет ни инкапсуляции, ни наследования реализации.

Безотносительно того, прав ли asaw в том, что ООП нужно, аргументы против точки зрения asaw-а выглядят довольно странно. Причем формально-то asaw прав: нет соответствия святой троице ООП — нет и ООП в языке.

eao197 ★★★★★
()
Ответ на: комментарий от pftBest

Нет, не скажу. Там есть инкапсуляция и есть extension методы. Или как в C++ - есть инкапсуляция, и есть free functions. А вот в Си инкапсуляции нет, есть только free functions, хотя даже вполне можно привязывать функции к объектам.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Добавление трейта ничем не отличается от добавления extension метода. И раз второе не нарушает инкапсуляцию по твоему мнению, значит и первое не должно.

pftBest ★★★★
()
Ответ на: комментарий от loyd

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

Так в этом то и предмет обсуждения: что непосредственно позволяет выразить язык. В Си тоже можно «не смотреть на синтаксис, а смотреть на семантику», и видеть там ООП, только это не означает, что в Си есть поддержка ООП.

Интереснее то, что наследование конфликтует с твоим определением инкапсуляции, потому что поведение задаётся не здесь, а в базовом классе.

Я здесь никаких своих определений не приводил, только общепринятые.

asaw ★★★★★
()
Ответ на: комментарий от eao197

Да, вот вы правильно уловили суть. Причем, я же не говорю, что полноценной поддержки ООП в расте никогда не будет потому что её там не может быть, я просто говорю, что сейчас там нет поддержки пары основных принципов. И именно это, по моему мнению, является причиной того, что некоторые проекты на Rust превратились в bloatware.

asaw ★★★★★
()
Ответ на: комментарий от asaw

И именно это, по моему мнению, является причиной того, что некоторые проекты на Rust превратились в bloatware.

Недавно как раз был соответствующий наброс: Why I’m dropping Rust. IIRC, человек там как раз жаловался на отсутствие наследования реализации.

Понятное дело, что православные Rust-оманы уже предали автора сего опуса обструкции :)

eao197 ★★★★★
()
Ответ на: комментарий от pftBest

Добавление трейта ничем не отличается от добавления extension метода. И раз второе не нарушает инкапсуляцию по твоему мнению, значит и первое не должно.

Как можно нарушить что-то, если его и так нет?)

asaw ★★★★★
()
Ответ на: комментарий от asaw

Так в этом то и предмет обсуждения: что непосредственно позволяет выразить язык.

Правильно. Раст позволяет выразить то, что ты подразумеваешь под инкапсуляцией. То, что для этого используются другие ключевые слова суди дела не меняет. А стоит ли так писать код, когда язык предоставляет лучшие концепции это второй вопрос.

loyd
()
Ответ на: комментарий от eao197

Причем формально-то asaw прав: нет соответствия святой троице ООП — нет и ООП в языке.

Инкапсуляция есть, что бы asaw не думал.

mod M {
    pub struct S {private_field: u32}
    impl S { pub public_interface(&self) {} }
}

Доступ к внутренностям S возможен только с помощью публичного интерфейса.

Открытой рекурсии нет (https://en.wikipedia.org/wiki/This_(computer_programming)#Open_recursion). Это верно. Впрочем, о проблемах открытой рекурсии известно давно (см. ссылки на литературу в википедии). Поэтому, кстати, и можно говорить о «кривизне С++/С#/Java».

Наследование реализаций для ООП не обязательно.

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

red75prim ★★★
()
Ответ на: комментарий от eao197

На что ему говорят, что в С++/C#/Java — кривое ООП, а вот в Rust-е то с ООП все нормально как раз из-за того, что в языке нет ни инкапсуляции, ни наследования реализации.

Кто это тут говорит из людей, которые хотя бы документацию раста освоили, что в языке нет инкапсуляции?

Ну а кривое ООП или не кривое в С++/С#/Java это сложный вопрос. С точки зрения какого-нибудь smalltalk-а там и не ООП вообще, в отличие от современного догмата «ООП = классовая модель + троица».

loyd
()
Ответ на: комментарий от asaw

Как можно нарушить что-то, если его и так нет?

Я не говорю что (классическое) ООП в Rust есть, я говорю только про инкапсуляцию, и она есть пока не доказано что ее нету (презумпция невиновности). Я все пытаюсь вытянуть из тебя что же все таки мешает расту иметь инкапсуляцию, но все твои аргументы сводятся к

Этого в расте нет, точка.

pftBest ★★★★
()
Ответ на: комментарий от eao197

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

Есть кадры которые изображают исключения на расте, просто потому, что привыкли так писать. А потом жалуются, что раст плохой. Но он ни при чём, просто вы используете инструмент не по назначению.

RazrFalcon ★★★★★
()
Ответ на: комментарий от pftBest

Я все пытаюсь вытянуть из тебя что же все таки мешает расту иметь инкапсуляцию

Отсутствие языковых средств для выражения состояния и поведения типа как единой сущности.

asaw ★★★★★
()
Ответ на: комментарий от asaw

А зачем так сложно, когда есть free functions?

А причем здесь free functions, если речь о трейте?

tailgunner ★★★★★
()
Ответ на: комментарий от loyd

вряд ли здесь какой-то rustоман будет утверждать, что там есть ООП

Не, ну можно попробовать, просто в целях флейма: ООП - это святая троица «полиморфизм, инкапсуляция и наследование»; полиморфизм в Rust есть, инкапсуляция тоже, и даже наследование есть - наследование трейтов (ведь святая троица ничего не говорит о том, какое именно наследование должно быть). Так что Rust - ООП-язык %)

tailgunner ★★★★★
()
Ответ на: комментарий от red75prim

Наследование реализаций для ООП не обязательно.

Тогда какой смысл в наследовании?

Формального определения, насколько я знаю, нет.

Если не договориться не терминах и определениях, тогда разговор теряет смысл.

eao197 ★★★★★
()
Ответ на: комментарий от asaw

Опять пришли к тому с чего начали, это средство называется модуль, и Rust гарантирует что ты не сможешь изменить состояние обьекта за пределом его модуля. Значит и состояние и поведение будут всегда находится в одном модуле как «единая сущность».
Вот я набросал пример улитки, можешь попробовать заставить ее полететь:
https://is.gd/nhpP3X
Если получится, пиши.

pftBest ★★★★
()
Ответ на: комментарий от loyd

Кто это тут говорит из людей, которые хотя бы документацию раста освоили, что в языке нет инкапсуляции?

А разве инкапсуляция не противоречит системе трейтов? Вот выше red75prim показал пример со структурой S. Если для реализации некоторого трейта T нужен доступ к приватному полю S, то что? Вынесение ацессора для этого поля в некий public_interface будет означать отказ от инкапсуляции.

С точки зрения какого-нибудь smalltalk-а там и не ООП вообще, в отличие от современного догмата «ООП = классовая модель + троица».

Боюсь, вы попутали SmallTalk с Self-ом и JavaScript-ом, емнип, в SmallTalk все нормально с классами, и со святой троицей. Кроме того, лично я вообще ничего про классы не говорил, ограничивая ООП только наследованием, инкапсуляцией и полиморфизмом.

eao197 ★★★★★
()
Ответ на: комментарий от pftBest

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

Этого в расте нет, точка.

А я, кажется, понял, о чем он - методы структуры не описываются в теле struct.

tailgunner ★★★★★
()
Ответ на: комментарий от eao197

Если для реализации некоторого трейта T нужен доступ к приватному полю S, то что?

То пишем реализацию внутри модуля M, или отправляем PR на реализацию трейта, если модуль чужой.

red75prim ★★★
()
Ответ на: комментарий от asaw

сейчас там нет поддержки пары основных принципов. И именно это, по моему мнению, является причиной того, что некоторые проекты на Rust превратились в bloatware.

Нет, не является. Твоё школьное определение инкапсуляции обладает нулевой практической ценностью, а наследование реализации успешно (пусть и не всегда с лёгкостью) заменяется композицией.

Esper
()
Ответ на: комментарий от asaw

У нас тут поведением и состоянием что обладает?

Модуль.

Какая сущность какого типа?

А где написано что у сущности должен быть тип? Ты пытаешься натянуть на определение инкапсуляции свойства класса, хотя совсем не обязательно иметь класс чтобы получить инкапсуляцию.
P.S. Жду свою летающую улитку.

pftBest ★★★★
()
Последнее исправление: pftBest (всего исправлений: 1)
Ответ на: комментарий от eao197

Если для реализации некоторого трейта T нужен доступ к приватному полю S, то что?

То ничего. Если это не твой модуль, то либо владелец оправданно это поле спрятал, либо у кого-то из вас проблема с проектированием.

Покажи как ты будешь организовывать доступ к приватному полю в c++, не применяя грязные хаки?

WatchCat ★★★★★
()
Ответ на: комментарий от pftBest

Модуль

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

asaw ★★★★★
()
Ответ на: комментарий от asaw

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

pftBest ★★★★
()
Ответ на: комментарий от tailgunner

Попробовать-то можно, но собеседник всё равно будет считать, что ООП это «как у меня в С++/Java/C#». Такие и JS за ООП язык не считают, чего уж там расту.

loyd
()
Ответ на: комментарий от WatchCat

Если это не твой модуль, то либо владелец оправданно это поле спрятал, либо у кого-то из вас проблема с проектированием.

Отлично.

В кривых ООЯ, вроде C++ и Java, есть возможность сделать protected члены (как атрибуты, так и методы), что дает возможность использовать наследование реализации и, при этом, переиспользовать части базового класса.

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

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

Ну и зачем после этого пытаться говорить, что в Rust-е поддерживается ООП?

eao197 ★★★★★
()
Ответ на: комментарий от AUX

Ну так многие и на C пишут, и дискомфорта не чувствуют.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

сли для реализации некоторого трейта T нужен доступ к приватному полю S, то что?

Не надо ничего выносить, нужно реализовать трейт для нашего S:

impl T for S {
  fn foo(&self) -> {
    // Есть доступ к приватным, но только если мы владельцы S
  }
}

Боюсь, вы попутали SmallTalk с Self-ом и JavaScript-ом, емнип, в SmallTalk все нормально с классами, и со святой троицей.

Self и JS тоже неплохой аргумент, но я говорил именно о SmallTalk-е и его создателе (авторе ООП, Алан Кей), который не считает С++ ООП-языком, например.

loyd
()
Ответ на: комментарий от eao197

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

Ты так говоришь, как будто protected это не «прощай инкапсуляция»

loyd
()
Ответ на: комментарий от loyd

// Есть доступ к приватным, но только если мы владельцы S

Начинаю подозревать, почему доводы asaw-а не понимают.

Self и JS тоже неплохой аргумент, но я говорил именно о SmallTalk-е и его создателе (авторе ООП, Алан Кей), который не считает С++ ООП-языком, например.

Чтобы называть Алана Кея автором ООП, нужно, блин, совсем ничего не знать про Симула.

Еще и Джо Армстронг считает Erlang образцом ООП, давайте еще и это сюда приплетем.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Птица-секретарь

Выдержка ниже. Взято из Википедии.

Симула традиционно считается первым в мире объектно-ориентированным языком, но создатель языка Smalltalk Алан Кэй утверждает, что изобрёл термин «ООП»
Simula 67 явилась первым языком с встроенной поддержкой основных механизмов объектно-ориентированного программирования. Этот язык в значительной степени опередил своё время, современники (программисты 60-х годов) оказались не готовы воспринять ценности языка Simula 67

anonymous
()
Ответ на: комментарий от loyd

Ну тогда расскажите, как же protected нарушает инкапсуляцию? Ведь если есть класс B, унаследованный от класса A, то экземпляр класса B отнюдь не перестает быть экземпляром класса A (не будем сейчас брать в расчет варианты с private-наследованием). Соответственно, атрибуты класса A остаются доступны только внутри класса A.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

Иногда заявляется даже большее: наследование [реализаций] нарушает инкапсуляцию, хотя по факту имеют в виду именно защищённые поля:

— «Because inheritance exposes a subclass to details of its parent's implementation, it's often said that inheritance breaks encapsulation» (у Банды Четырёх)

— «Unlike method invocation, inheritance violates encapsulation [Snyder86]. In other words, a subclass depends on the implementation details of its superclass for its proper function.» у Д. Блоха.

— «But then closely related concepts should not be separated into different files [..] Indeed, this is one of the reasons that protected variables should be avoided.» у Б. Мартина в Clean Code.

— Ещё стоит поискать «inheritance breaks encapsulation» у Макконнелла.

loyd
()
Ответ на: комментарий от eao197

Ну тогда расскажите, как же protected нарушает инкапсуляцию?

Если инкапсуляция по терминологии asaw то, реализация отдельно от объявления полей - нарушение инкапсуляции.

anonymous
()
Ответ на: комментарий от loyd

базовый класс недостаточно инкапсулирован от производных

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

Пожалуй сольюсь и оставлю экспертов выдумывать про ООП новые небылицы.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Ведь если есть класс B, унаследованный от класса A, то экземпляр класса B отнюдь не перестает быть экземпляром класса A [...]

Только в том случае, если выполняется принцип подстановки Лисков, а для этого недостаточно написать class B: A.

red75prim ★★★
()
Ответ на: комментарий от red75prim

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

class MyIntVector {
  protected :
    int * begin_;
    int * end_;
    size_t capacity_;
  public :
    ... // Ни одного виртуального метода.
};
...
class MyIntVectorWithDebugPrint : public MyIntVector {
  public :
    void debug_print() const {
      std::cout << this << ": capacity=" << capacity_
        << ", begin=" << begin_ << ", end=" << end_
        << ", size=" << (end_ - begin_) << std::endl;
    }
};

Прошу прощения, но дальше без меня.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Логику же Rust-оманов в данном случае вообще понять не могу. Ну вот есть устоявшееся определение ООП. Ну не подходит Rust под это определение

Внезапно: средства поддержки ООП бывают разные. Про смоллтолк и джаваскрипт уже написали. Кстати, привет asaw'у с его неприятием добавления трейта к существующему классу, интересно, что он думает о js с возможностью менять поведение прямо в рантайме у конкретного экземпляра:D. Добавлю, что даже внутри «семьи» c++ наследование и инкапсуляция реализованы существенно по-разному. C++ позволяет множественное наследование интерфейсов, данных и поведения, притом есть 2 варианта наследования, простое и виртуальное. C# позволяет одиночное наследование данных и поведения, и множественное наследование интерфейсов. Java позволяет одиночное наследование данных и множественное наследование интерфейсов и поведения. Раст в этом плане недалеко ушёл, всего лишь запретили наследование данных. В плане инкапсуляции тоже по-разному, в c++ нагородили protected и friend, косящие под c++, Java и c# унаследовали всё, кроме френдов, вместо них добавив internal и «default», rust же решили ограничится закрытием реализации внутри модуля. Для впитавшего c++ с молоком матери это кажется святотатством, но если забыть на минуту заветы старика Страуструпа и попытаться подумать о практическом смысле, станет ясно, что все эти методы ограничения нужны только для того, чтоб указать, мол вот эту часть можете использовать, а вот эта часть - не ваше дело, могу менять как хочу и не гарантирую надёжную работу при неправильном обращении. В этом плане закрытие реализации внутри модуля как раз является естественным, одновременно есть доступ у кого надо и толко у кого надо. Можно смело менять потроха, не боясь поломать внешние зависимости (естественно, при условии неизменности интерфейса). Собственно вся эта пурга с private/protected/friend была добавлена в C++ не от хорошей жизни, а от отсутствия модулей и даже неймспейсов в момент зарождения языка. Раст не пытается играть в совместимость с C++ и умеет модули от рождения, логично что способ разграничения интерфейса и потрохов заменили на более пристойный.

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Внезапно: средства поддержки ООП бывают разные.

Внезапно: речь не про то, хорош ли подход Rust-а или плох, а про то, насколько правильно говорить о том, что Rust поддерживает ООП.

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

Добавлю, что даже внутри «семьи» c++ наследование и инкапсуляция реализованы существенно по-разному.

Вы еще добавьте, что это семья Симулы, а не C++. И что в мире статически-типизированных языков есть еще и Eiffel с нормальным множественным наследованием. А то ваш поток сознания не выглядит достаточно авторитетно.

ЗЫ. Менять поведение конкретного экземпляра в рант-тайме можно не только в JS, но и в SmallTalk/Ruby и, емнип, в Python-е. Это имеет большее отношение к динамической типизации, нежели к ООП.

eao197 ★★★★★
()
Ответ на: комментарий от AUX

Тем не менее троица из инкапсуляции, наследования и полиморфизма есть во всех успешных ООЯ. Так что мне не о чем беспокоится.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

В Golang нет наследования, но он успешен.

AUX ★★★★
()
Ответ на: комментарий от eao197

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

Боюсь, это только в терминологии asaw понимается так буквально. Причём это не шибко-то работает даже для C++, так как данные-члены объявляются в .h файле, а работа с ними - в .cpp. В реальности инкапсуляция названа так из аналогии с другими областями, там под этим понимают изоляцию чего-то внутри другого чего-то, отсюда и общий корень с капсулой. Капсула - публичный интерфейс. Внутри - закрытая реализация. Наследование, как таковое, не всегда нарушает инкапсуляцию, но дело в том, что в практических случаях это происходит довольно часто. Поэтому и предлагают заменять наследование композицией, при композиции нет неявных зависимостей, делегация «предку» производится явно и только через его публичный интерфейс. Чисто для примера.

class List<T>:IList<T>
{
   public void insert(int index, T item){...}
   public void add(T item){...}
...
}
Пусть мне нужно реализовать отладочный список, который печатает добавляемый элемент или там наблюдаемый, который сообщает о добавлениии элементов. Что переопределяем? Если в List<T> add(T item) реализован через insert(Count, item), то нужно переопределять только insert, иначе нужно переопределить и insert и add. Вот и нарушение инкапсуляции, зависимость от подробностей реализации, причём даже без каких-либо попыток доступа к защищённым членам.

khrundel ★★★★
()
Ответ на: комментарий от eao197

про то, насколько правильно говорить о том, что Rust поддерживает ООП.

Чего тут обсуждать? Поддерживает.

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

Ну вот вы сами и ответили. В C - вручную. А в расте не вручную. Вопрос можно закрывать.

Вы еще добавьте, что это семья Симулы, а не C++. И что в мире статически-типизированных языков есть еще и Eiffel с нормальным множественным наследованием. А то ваш поток сознания не выглядит достаточно авторитетно.

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

khrundel ★★★★
()
Ответ на: комментарий от loyd

С точки зрения какого-нибудь smalltalk-а там и не ООП вообще, в отличие от современного догмата «ООП = классовая модель + троица».

«When I invented OOP, i wasn't mean C++» (c) Alan Key

В классическом смоллтоке (что становится ясно если почитать статьи Алана Кея, например ранняя история смоллтока) была реализована модель акторов Карла Хьюита + пару инсайтов Кея + реализация от Дэна Ингалса (который не знал CL и CLOS, например, а знал бейсик. и поэтому изобрёл что-то отличное от CLOS)

эта идея с 70х прослеживается в : SmallTalk, Self, JavaScript, Pony, Scheme.

Scheme вообще появился в конце тех же 70х как скрещивание лямдба-исчисления с моделью акторов (ну и уже позже 'let over lamda' Дага Хойта, как осмысление подхода)

да, там не «ООП = классовая модель + троица».

а, скорее «ООП = метаобъектная модель + динамическая диспетчеризация»

динамическая диспетчеризация позволяет реализовать прокси-объекты, протоколы и категории в Objective C (то есть, более reusable интерфейсы)

в объектной модели COS эта тема ещё более развёрнута: «мультидиспетчеризация и мультипараметризация»

вообще, надо различать ООП как идею, концепцию, метапарадигму :-)

и конкретную реализацию объектной модели (например, ООП как в Simula67/C++85, Beta : «ООП = классовая модель + троица» )

anonymous
()
Ответ на: комментарий от anonymous

например, в CLOS/CL другая объектная модель. в смоллток — третья. в Eiffel — четвёртая (похожая на классическую троицу, но более reusable: reversibility/seamlessness, BON method, Eiffel method, Design By Contract, компиляторно-вычисляемый virtual, частично абстрактный/абстрагированный в потомках метод/свойство и наоборот(из абстрактного в конкретное), замена в потомках свойства на метод и наоборот, ковариантность/контравариантность, retry в исключениях, catcalls, SCOOP.)

anonymous
()
Ответ на: комментарий от khrundel

Боюсь, это только в терминологии asaw понимается так буквально. Причём это не шибко-то работает даже для C++, так как данные-члены объявляются в .h файле, а работа с ними - в .cpp.

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

asaw ★★★★★
()
Ответ на: комментарий от eao197

Ни одного виртуального метода

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

red75prim ★★★
()
Ответ на: комментарий от red75prim

Кажется понял, что asaw так долго пытался сказать. Используя наследников класса А, мы можем использовать особенности реализации открытого интерфейса А конкретным классом А. Если мы не используем наследование реализации, то мы должны завязываться только на спецификацию открытого интерфейса А.

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

В этом смысле наследование реализации провоцирует нарушение инкапсуляции.

red75prim ★★★
()
Ответ на: комментарий от asaw

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

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

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Я написал, что отделение деталей реализации от интерфейса это и есть инкапсуляция.

нет, это совершенно разные, хотя возможно и связанные вещи

инкапсуляция это невозможность нарушить инварианты объекта; например, у нас есть объект time_interval и для любого такого объекта всегда

time_interval.start() + time_interval.length() == time_interval.end()

то это инкапсуляция вместе с отделением деталей реализации от интерфейса; а если у нас

time_interval.start + time_interval.length() == time_interval.end

или

time_interval.start + time_interval.length == time_interval.end()

или

...

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

а если же можно так хитро вызвать какие-то методы, чтобы равенство

time_interval.start() + time_interval.length() == time_interval.end()

оказалось нарушено, то это отделение деталей реализации от интерфейса, но без инкапсуляции

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 1)
Ответ на: комментарий от WatchCat

Ну т.е. наследования нет, а ООП есть... Я же говорю, тут разговор зашел о таких высоких понятиях, осознать которые у меня уже не получается.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Ну т.е. наследования нет, а ООП есть... Я же говорю, тут разговор зашел о таких высоких понятиях, осознать которые у меня уже не получается.

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

и в общем-то дальше плевать на определение ооп (ну не совсем конечно; интерфейсы, ко- и контра-вариантность и мультиметоды нужны, и еще meta-object protocol по мере возможности тоже хотелось бы иметь)

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

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 2)
Ответ на: комментарий от eao197

а насчет наследования реализации — оно нужно, но не все с ним так просто, я напишу потом

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от khrundel

более точно, конечно, сказать не «инкапсуляция это невозможность нарушить инварианты объекта»

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

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 1)
Ответ на: комментарий от www_linux_org_ru

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

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

khrundel ★★★★
()
Ответ на: комментарий от eao197

Я же говорю, тут разговор зашел о таких высоких понятиях, осознать которые у меня уже не получается.

а что там осознавать?

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

«высокое понятие», ога

ООП «в виде реализации объектной модели runtime языка» — это понимание этого высокого понятия в конкретном языке.

при этом у каждого языка идеи ООП понимание и реализация идеи свои, разные.

* механизм реализации КОП — ООП (хотя может быть и не ООП как расширение АТД, а АТД + более reusable интерфейсы)

* механизм реализации ООП как идеи — реализация какой-то объектной модели ООП в каком-то языке.

* понимание того, что такое «повторно используемый модуль» (компонент) и как устроена система типов модулей, отношения в системе типов — разные. насколько получается composable и compositable.

* механизмы реализации объектной модели — разные, но в основном относятся к полиморфизму какого-то вида системы типов. и отношений. например, динамическая/статическая диспетчеризация по типам объектов, или типам типов объектов, типам метаобъектов и т.п.

* механизмы реализации интерфейсов повторно используемых компонент — тоже разные. например, трейты. метаклассы. метаобъектный протокол.

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

поэтому, как пишет Пфистер или Клеменс Шипёрски в книге «КОП» (например)

КОП = ООП + модульность «лучше чем в С++» + безопасность - наследование реализации через границы модулей.

то есть, реализация модульной системы, как КОП среды

(с рефлексией, фабриками объектов по запросу, репозиторием сервисов типа SOA и т.п.)

anonymous
()
Ответ на: комментарий от anonymous

Следующая таблица дает историческую перспективу эволюции императивного программирования:\

Десятилетие 	Технология программирования 	Ключевое нововведение
1940-е 	машинные коды 	программируемые машины
1950-е 	ассемблерные языки 	символы
1960-е 	языки высокого уровня 	выражения и независимость от машины
1970-е 	структурное программирование 	структурные типы и управляющие конструкции
1980-е 	модульное прогаммирование 	отделение интерфейса от реализации
1990-е 	объектно-орентированное пр-е 	полиморфизм
2000-е 	компонентно-ориентированное пр-е 	[b]динамическая и безопасная композиция[/b]

Таблица 4-1. Эволюция императивного программирования

anonymous
()
Ответ на: комментарий от anonymous

Тут проблемка. Вы специально не написали сюда тупиковые ветви типа функционального программирования? И с чего вы взяли что КОП не тупиковая ветвь. Истории успеха то нет.

anonymous
()
Ответ на: комментарий от eao197

Наследование никогда не было прибито гвоздями к данным или реализации.
А интерфейсы в Rust наследоваться могут.

WatchCat ★★★★★
()
Ответ на: комментарий от WatchCat

Наследование никогда не было прибито гвоздями к данным или реализации.

Если не наследуются ни данные, ни реализации, то в чем тогда смысл наследования? И если для статически-типизированных языков здесь хоть что-то можно приплести за уши, то как тогда быть с динамически-типизированными языками вроде SmallTalk, Ruby и Python?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

то в чем тогда смысл наследования

В наследовании интерфейса, ваш КО.
Кроме того, у трейтов есть ещё дефолтные методы.

WatchCat ★★★★★
()
Ответ на: комментарий от WatchCat

В наследовании интерфейса, ваш КО.

Т.е. заморочки статической типизации выдаем за один из элементов ООП.

Никто так и не ответил, зачем Rust-у нужно быть ООЯ. Так зачем?

eao197 ★★★★★
()
Ответ на: комментарий от asaw

там и инкапсуляции нормальной нет. Это уже обсуждали.

Не припоминаю толковых аргументов на эту тему. Ссылка или нормальное объяснение будет?

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Т.е. заморочки статической типизации выдаем за один из элементов ООП.

Где заморочки?

Никто так и не ответил, зачем Rust-у нужно быть ООЯ.

А кто-то спрашивал?
ООП, пусть и своеобразное, там в качестве вишенки.
Ему не надо, но он может.

WatchCat ★★★★★
()
Ответ на: комментарий от WatchCat

Где заморочки?

В наследовании интерфейсов. В динамически-типизрованных языках этих заморочек нет. Ваш К.О.

А кто-то спрашивал?

Да тут полтемы — это обсуждение того, является ли Rust ООЯ или нет. Растоманы говорят, что является, не смотря на специфический взгляд на инкапсуляцию и отсутствие наследования.

Ему не надо, но он может.

И C может. И Go.

Объектной-ориентированности это не добавляет ни тому, ни другому.

Как, например, наличие лямбд не делает C++, Java и C# функциональными языками. Хотя, при желании, на них можно и в таком стиле писать.

eao197 ★★★★★
()
Ответ на: комментарий от asaw

Мне ничего не помешает, например, приделать к этому же самом объекту новое поведение в другом месте.

И чем это плохо?

Опять же, в С++, до недавнего времени, тебе тоже никто не мешал (наследоваться и) приделывать крылья. Сейчас появилось ключевое слово final, но не уверен, что его стоит всем подряд библиотечным классам лепить. И уж точно нет никаких средств проконтролировать, что класс расширяют предусмотренным способом: можно выбрать или полный запрет расширения функциональности или мириться с тем, что класс будут «использовать неправильно.

Опять же, что мешает иметь делать обёртки/адаптеры/декораторы? Или экстеншн методы (а то и просто свободные функции)? К приватным данным доступа в расте точно так же не будет. В общем, ты упорно проблему из пальца высасываешь.

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

В наследовании интерфейсов.

Где в наследовании интерфейсов заморочки?

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

Чем он специфичен? Если не считать твоей зацикленности на protected.

И C может. И Go.

С не может - нет инкапсуляции и наследования в каком либо виде.
Про Go не знаю ничего.

Ещё раз.
В Rust есть:
- Инкапсуляция (A language mechanism for restricting direct access to some of the object's components.)
- Наследование. Да, только интерфейсов, но тем не менее.
- Полиморфизм.
- Абстракция.
Следовательно Rust объектно-ориентированый язык.
Всё. Дальше не вижу смысла спорить.

WatchCat ★★★★★
()
Ответ на: комментарий от khrundel

Раст в этом плане недалеко ушёл, всего лишь запретили наследование данных.

Неверно. Объявляем Worker и Human со «свойством» name, для первого реализуем трейты Deref и DerefMut. Теперь мы можем у работника вызывать публичные методы, которые технически принадлежат человеку, а также передавать Worker в функцию, которая требует Human. Чем не наследование данных? Вот только из-за отсутствия сахарка реализация этих 2-ух примитивных объектов занимает 43 строки без учета функции main(). Смотреть код.

shaiZaigh
()
Ответ на: комментарий от WatchCat

Где в наследовании интерфейсов заморочки?

Хватит тупить. Само понятие «интерфейса» — это заморочки статической типизации.

Чем он специфичен?

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

Следовательно Rust объектно-ориентированый язык.

С хера ли?

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

eao197 ★★★★★
()
Ответ на: комментарий от DarkEld3r

И чем это плохо?

Это не просто «не плохо», это офигенно классно сделано, так и должно быть!

В общем, ты упорно проблему из пальца высасываешь.

Оно или троллит, или умственно ограниченное. В любом из этих двух случаев, конструктивный диалог не выстроить.

Manhunt ★★★★★
()
Ответ на: комментарий от eao197

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

Осталось обосновать тезис о том, что ООП без наследования реализации не бывает :D

Manhunt ★★★★★
()
Последнее исправление: Manhunt (всего исправлений: 1)
Ответ на: комментарий от asaw

Ты можешь убедительно доказать, что ООП не нужно, эффективно реализовав на языке без поддержки ООП сложный качественно работающий продукт

Хочешь сказать, что тех же чисто функциональных языках ничего сложного не написали? Или опять пойдут отмазки в духе «ну браузерного движка нет» или «на джаве/шарпе/плюсах больше написано»?

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Само понятие «интерфейса» — это заморочки статической типизации

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

anonymous
()
Ответ на: комментарий от eao197

Хватит тупить.

А ты перестань упарываться всякой химией

Само понятие «интерфейса» — это заморочки статической типизации.

Обоснуй.

Отсутствие наследования реализации говорит о том,

Не говорит.
Это ты сейчас выдумал. Как и вышепроцитированный тезис.

WatchCat ★★★★★
()
Ответ на: комментарий от Manhunt

Не менее необходимо доказать, что наследование интерфейсов — это есть то самое наследование из святой троицы ООП ;)

eao197 ★★★★★
()
Ответ на: комментарий от WatchCat

Обоснуй.

Как все запущено. Пожалуй, сольюсь, объяснять азы столь самоуверенному эксперту нет возможности.

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

Это всё равно что саму статическую типизацию объявить заморочкой.

С точки зрения динамической типизации это так и есть.

Интерфейсы есть всегда, только в динамических языках они неявные, а в статических - явные, как и всё что касается типов.

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

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

eao197 ★★★★★
()
Ответ на: комментарий от shaiZaigh

Смешно. Нужно внести в анналы как растоманы друг с другом играют в слова. Да, в других языках наследование данных тоже (внезапно) реализуется с помощью композиции, только для этого не нужно ещё попутно жонглировать стоя на ушах.

asaw ★★★★★
()
Последнее исправление: asaw (всего исправлений: 1)
Ответ на: комментарий от eao197

Т.е. заморочки статической типизации выдаем за один из элементов ООП.

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

Никто так и не ответил, зачем Rust-у нужно быть ООЯ. Так зачем?

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

khrundel ★★★★
()
Ответ на: комментарий от eao197

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

Нет. Это ортогональные понятия. Более того, использование множественного наследования для реализации интерфейсов - это костыль. В GNU Си++ была (в 1994 году) такая вещь, как сигнатуры - почти что трейты Rust: https://gcc.gnu.org/onlinedocs/gcc-2.95.3/gcc_5.html#SEC112 Но не прижилось :/

tailgunner ★★★★★
()
Ответ на: комментарий от khrundel

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

Это вы троллите или реально не понимаете?

Я не понял, вы сейчас торгуетесь что ли?

Не поняли.

Rust выглядит как язык, который предлагает несколько другие инструменты для борьбы со сложностью. В частности это абстрактные типы данных, алгебраические типы данных, обобщенное программирование. Очень нехилый набор инструментов. Особенно если сравнивать с C или Go.

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

Типа, это не говно, я не зря на него перешел?

eao197 ★★★★★
()
Последнее исправление: eao197 (всего исправлений: 1)
Ответ на: комментарий от tailgunner

Нет. Это ортогональные понятия.

Да. Не ортогональные.

В Eiffel-е, например, интерфейсы не нужны. Ибо там смогли во множественное наследование.

В C++ с множественным наследованием похуже, но в нем так же интерфейсы не нужны.

А вот в Java и его потомках, от множественного наследования отказались (ибо не смогли как в Eiffel), отсюда и пошло проникновение интерфейсов в мейнстрим.

eao197 ★★★★★
()
Ответ на: комментарий от asaw

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

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

А ничего, потому что это не проблема.

DarkEld3r ★★★★★
()
Ответ на: комментарий от asaw

Нет, тут смысл в том, что в C++ ты для этого создаешь НОВЫЙ ТИП, а в расте ты можешь волшебным образом добавить новое поведение старому.

Во первых, если работать с интерфейсами, то для кода на С++ создание нового типа пройдёт незамеченным. Во вторых, в расте надо явно импортировать трейт, методами которого хочешь пользоваться.

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Вы столько времени пытаетесь донести до Rust-оманов простые и очевидные вещи, но все как горохом о стену.

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

asaw ★★★★★
()
Последнее исправление: asaw (всего исправлений: 1)
Ответ на: комментарий от eao197

Нет. Это ортогональные понятия.

Да. Не ортогональные.

Нет. Сходи по ссылке.

А вот в Java и его потомках, от множественного наследования отказались (ибо не смогли как в Eiffel), отсюда и пошло проникновение интерфейсов в мейнстрим.

Да какая разница, откуда они проникли в мейнстрим. Они были в языка с множественным наследованием (Си++), с одиночным (Objective C), и даже вектор операций в Си - тоже интерфейс.

tailgunner ★★★★★
()
Ответ на: комментарий от DarkEld3r

Я под инкапсуляцией понимаю возможность работы с данными только из методов объекта.

Сокрытие реализации — это несколько другая тема.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Не менее необходимо доказать, что наследование интерфейсов — это есть то самое наследование из святой троицы ООП ;)

Вот на тебе определение сути ООП от человека, изобретшего само понятие ООП (обрати внимание, никакими троицами тут не пахнет):

http://c2.com/cgi/wiki?AlanKaysDefinitionOfObjectOriented

I have asked Alan Kay about his definition of «object oriented» and he told me in 2003:

OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme LateBinding of all things.


Вот на тебе определение ООП из википедии (что характерно, тоже без троиц):

https://en.wikipedia.org/wiki/Object-oriented_programming

Object-oriented programming (OOP) is a programming paradigm based on the concept of «objects», which may contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods. A feature of objects is that an object's procedures can access and often modify the data fields of the object with which they are associated (objects have a notion of «this» or «self»). In OOP, computer programs are designed by making them out of objects that interact with one another.


Так что ваше увлечение троицами — это ваши личные, интимные проблемы. Жесткая связь между ООП и ортодоксальной «троицей» есть только в ваших субъективных преставляениях об ООП.

Лично для меня квинтессенция ООП — это принципы SOLID. И rust к ним вполне приспособлен.

Manhunt ★★★★★
()
Ответ на: комментарий от tailgunner

Да какая разница, откуда они проникли в мейнстрим.

Разительная.

Они были в языка с множественным наследованием (Си++)

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

eao197 ★★★★★
()
Ответ на: комментарий от Manhunt

Лично для меня квинтессенция ООП — это принципы SOLID.

Ну найдите упоминания о SOLID-е в процитированных вами определениях. Потом расскажите всем о собственных интимных проблемах.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Ну найдите упоминания о SOLID-е в процитированных вами определениях.

Так ведь я, в отличие от вас, не пытаюсь подменить solid-ом само определение ООП. Давайте уже по существу: Rust 1.12 (комментарий)

Manhunt ★★★★★
()
Ответ на: комментарий от eao197

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

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

И снова непонятно слово «заморочка». Интерфейс — вполне естественное понятие, вытекающее из статической типизации. Для меня заморочка - это скорее отсутствие явных интерфейсов в динамических языках. И эмуляция интерфейсов на классах как в C++ - тоже заморочка, не вполне естественная.

Также непонятны отсылки ко множественному наследованию — вроде оно ни в каких определениях ООП не фигурирует.

anonymous
()
Ответ на: комментарий от Manhunt

Вы как-то поздно спохватились. Rust 1.12 (комментарий) Троица уже давно была упомянута в треде. И только сейчас вы решились прибегнуть к схоластике и доказать, что упомянутая троица — это не ООП.

Ну удачи, чо.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

сам факт их исчезновения из языка с множественным наследованием говорит сам за себя:

Факт их исчезновения говорит о том, что идея отвергается, если ее время не пришло.

в языке с нормальным множественным наследованием отдельное понятие интерфейса является лишней (т.е. не нужной) сущностью.

Как знать. Разговоры о runtime concepts - это неспроста.

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

Если помнишь, Гослинг, например, размышлял об полном отказе от наследования.

Не помню. Помню, как говорилось, что в Java решили не включать множественное наследование дабы избежать сложностей, которые были в C++ (ведь Java позиционировалась тогда как «правильно сделанный C++»). И это по официальной версии.

И снова непонятно слово «заморочка».

Поясню. Я специально характеризовал ООП в этом треде как троицу из наследования, инкапсуляции и полиморфизма. Ибо эти три качества, как показала история и практика, обнаруживаются в (насколько я помню) большинстве ООЯ вне зависимости от того, статически они типизированы или динамически, используются ли в них классы (как в наследниках Simula) или прототипы (как в наследниках Self-а).

Т.е. не смотря на то, что ООП — это довольна размытая штука, которая в зависимости от реализации может сильно отличаться в деталях, но есть понятия, которые для ООП критически важны. Та самая троица.

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

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

eao197 ★★★★★
()
Ответ на: комментарий от tailgunner

Как знать. Разговоры о runtime concepts - это неспроста.

Как по мне, так это не имеет отношения к ООП.

C++ ведь никогда не был чистым ООЯ (в отличии от Eiffel-я и Java). В свое время он вбирал идеи из функциональщины (емнип, шаблоны в C++ — это заимствование из ML и Ada), и продолжает это делать. В эту же сторону и концепты.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Это вы троллите или реально не понимаете?

Реально не понимаю. Вот C - язык со статической типизацией. И без динамической диспетчеризации. И живёт. Раст, если бы в нём не было trait object'ов тоже был бы языком со статической типизацией и без динамической диспетчеризации. Трейты использовались бы только для ограничения дженериков. С другой стороны можно представить язык с динамической типизацией, в котором при компиляции методы всех классов собираются в один список и для каждого фактического класса создаётся виртуальная таблица, в которой просто чужие методы забиты заглушками.

Rust выглядит как язык, который предлагает несколько другие инструменты для борьбы со сложностью. В частности это абстрактные типы данных, алгебраические типы данных, обобщенное программирование. Очень нехилый набор инструментов. Особенно если сравнивать с C или Go.

Безусловно так и есть. Если вы под ОО языком понимаете язык, зацикленный на ОО парадигме, то да, Rust к таким не относится. Но поддержка ОО, тем не менее, в язык интегрирована.

И вот не понятно, зачем утверждать «Раст просто по факту язык, поддерживающий ОО»?

Потому что есть в нём инструменты для ОО. Вот сравнить с C#, в нём наследование данных и методов ограничено 1 предком, а в Rust'е данные ограничены 0, зато методы можно наследовать от неограниченного количества предков. Вот я почему-то уверен, что 20 лет назад C++ программисты, с опытом работы над старым проектом (без RTTI), когда читали книгу «java за 21 день» точно так же говорили, что java - не полноценный ОО язык, так как предок всего 1, а instanceof - это корявка для слабаков.

Типа, это не говно, я не зря на него перешел?

Тут, по-моему, только asaw считает, что поддержка ООП - это то, без чего язык неполноценен.

khrundel ★★★★
()
Ответ на: комментарий от eao197

Разговоры о runtime concepts - это неспроста.

Как по мне, так это не имеет отношения к ООП.

Cами интерфейсы - это не ООП, поэтому выражение интерфейсов с помощью ООП (наследования в Си++) неестественно. Но у трейтов конкретно Rust есть операция наследования, и вот это уже можно (при желании) считать ООП.

tailgunner ★★★★★
()
Ответ на: комментарий от khrundel

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

Это противоречит самой природе динамической типизации.

Если вы под ОО языком понимаете язык, зацикленный на ОО парадигме, то да, Rust к таким не относится.

Я уже давно обозначил о чем идет речь: если язык из коробки поддерживает троицу «инкапсуляция, наследование и полиморфизм», то это ООЯ.

При этом ООЯ не обязательно должен быть «чистым ООЯ». В качестве примеров: С++ и OCaml, которые поддерживают ООП, но остаются мультпарадигменными.

Тут, по-моему, только asaw считает, что поддержка ООП - это то, без чего язык неполноценен.

Безотносительно того, прав ли asaw или нет, я лично не понимаю, почему растоманы так рьяно бросились доказывать, что Rust — это ООЯ. В Rust-е свой набор инструментов, как и, скажем, в Haskell-е. Возможно, в каких-то классах задач инструментарий Rust-а гораздо удобнее, чем таковой в Java или C#, или в C++.

eao197 ★★★★★
()
Ответ на: комментарий от tailgunner

Cами интерфейсы - это не ООП, поэтому выражение интерфейсов с помощью ООП (наследования в Си++) неестественно.

Можно и так сказать. Как по мне, так интерфейсы — это инструмент для поддержки абстрактных типов данных.

ООП так же может использоваться для реализации абстрактных типов данных.

Тем не менее, АТД и ООП — это разные вещи. Поэтому, на мой взгляд, Rust — это язык с АТД (и АлгТД в придачу), но вот ООП в нем в чистом виде нет.

eao197 ★★★★★
()
Ответ на: комментарий от khrundel

я тут со всеми на ты общаюсь (в обе стороны)

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

если интересно именно это обсудить — то инкапсуляция это только один из способов обеспечить соблюдение инварианта

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

В качестве примера, предоставление доступа к внутренностям в режиме только для чтения никак не может повредить состоянию объекта, и тем не менее инкапсуляция в таком случае сомнительна.

беря твое же упоминание про капсулу (я с ним согласен) — капсула может быть прозрачной, и это инкапсуляции не вредит

вообще речь у нас о терминах, и там могут быть разные мнения — вон википедия пишет, что не все разделяют деление на инкапсуляцию и information hiding

но мой пойнт в том, что инварианты нужны железно (в смысле обязательно), а вот information hiding может быть под вопросом

скажем, если мы проектируем тип Time с обычными операциями Time::get_year(), Time::set_year(int), ... Time::get_second(), Time::set_second(int), то можно и позволить например time.set_second(100500), которая будет переводить секунды в минуты, часы и т.п. и добавлять их тоже, а не только ставить секунды (примерно как в ui андроида)

но обязательно get_second() после этого не должна возвращать 100500 — это будет нарушением инварианта

а теперь information hiding — нужно ли предоставлять get_seconds_since_epoch(), где эпоха не юниксовая, а некая своя? можно *спорить* на тему, что этот метод выдает наружу деталь реализации — именно спорить

еще пример: пусть нам нужен класс Point { float x; float y;} про который известно, что x и y имеют точность существенно ниже аппаратного float

и как ты будешь скрывать деталь реализации — а именно, последние мусорные биты float-а? скорее всего, никак

да, можно наколбасить на шаблонах imprecise_float, но у него *все* операции будут ровно те же, что и у обычного float-а, и смысл его будет только в том, чтобы где-то компилятор ругнулся на его конверсию в обычный float; вряд ли этот imprecise_float действительно будет полезным при разработке софта

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 3)
Ответ на: комментарий от eao197

Поэтому, на мой взгляд, Rust — это язык с АТД (и АлгТД в придачу), но вот ООП в нем в чистом виде нет.

Если чистым видом считать Java и Си++, с наследованием реализации - его нет. Но, с другой стороны, что мы теряем? Ну да, бывает удобно конструировать типы наследованием. Вопрос только в том, достаточно ли это часто - если нет, то приведенный выше трюк с перегрузкой Deref вполне жизнеспособен.

tailgunner ★★★★★
()
Ответ на: комментарий от eao197

Проблема в том, что вы вполне здравую и актуальную на сегодняшний день идею ООП пытаетесь подменить бестолковой троицей. На каком-то этапе развития (20-30 лет назад, да) действительно казалось, что троица удачно детализирует само понятие ооп. С тех пор было собрано немало опыта, сделано выводов, и на сегодняшний день место троицы — где-то на обочине той дороги, по которой движется ооп. http://programmers.stackexchange.com/questions/253090/why-are-inheritance-enc...

Manhunt ★★★★★
()
Ответ на: комментарий от eao197

Не помню

http://www.artima.com/intv/gosling34.html

Размышления пост-фактум, но они показывают что даже одиночное наследование не считалось необходимым.

Поясню

Мысль понял, но посмотри на это с другой стороны: если стоит выбор между множественным наследованием и интерфейсами, то что замороченнее, причем и для разработчиков языка, и для его пользователей?

Множественное наследование потенциально мощнее, но эта мощь нужна слишком редко, чтобы так всё усложнять (сужу по C++), и проще будет написать чуть больше кода. История ЯП эту мысль подтверждает (а отдельно интересна тенденция вообще отказываться от наследования в новых языках).

anonymous
()
Ответ на: комментарий от tailgunner

Если чистым видом считать Java и Си++, с наследованием реализации - его нет.

О том и речь. Безотносительно того, хорошо это или плохо.

Но, с другой стороны, что мы теряем?

Это уже совсем другой вопрос.

eao197 ★★★★★
()
Ответ на: комментарий от Manhunt

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

Проблема в том, что сейчас ООП в статически-типизированных языках — это уже не ООП в чистом виде.

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

А троица как описывала ООП, так и продолжает описывать.

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

eao197 ★★★★★
()
Ответ на: комментарий от eao197

А троица как описывала ООП, так и продолжает описывать.

Троица описывает устаревший, имеющий серьезные изъяны, взгляд на ооп. ООП — это про локализацию и сокрытие state внутри объектов, про позднее связывание и про обмен сообщениями. Если опуститься до реализации, то чаще всего state заключен в аттрибутах объектов, позднее связывание достигается за счет того что объекты друг о друге мыслят в терминах абстракций (*контрактов*, и их бедных родственников - интерфейсов, трейтов, концептов), а обмен сообщениями осуществляется путем вызова стоящих за абстракциями конкретных связанных с конкретными данными процедур. Вполне возможны какие-то другие реализации, и это всё ещё будет ооп. А «троица» здесь попросту не релевантна.

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

Я взял свой любимый solid, и вижу, что про троицу можно с чистой совестью забыть, и ничего при этом не потеряешь.

Manhunt ★★★★★
()
Последнее исправление: Manhunt (всего исправлений: 2)
Ответ на: комментарий от Manhunt

Я взял свой любимый solid, и вижу, что про троицу можно с чистой совестью забыть, и ничего при этом не потеряешь.

Прекрасно. Давайте считать C объектно-ориентированным языком.

eao197 ★★★★★
()
Ответ на: комментарий от tailgunner

я чуть сократил (ненужные изменения имени) и добавил печать и профессию рабочему https://play.rust-lang.org/?gist=292bdef40f50f8016d34f21088312d97&version...

то приведенный выше трюк с перегрузкой Deref вполне жизнеспособен.

хм? возможно после сильной доработки, т.к. перегрузка Deref всегда возвращает указатель на Human-а

как сделать так, чтобы у обоих — и Human, и Worker была функция say, и у человека она печатала

println!(«I am {}», human.name );

а у рабочего че-то типа

println!(«I am {} and I am {}», human.name, profession );

похоже там еще придется делать таблицу виртуальных функций, не?

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 1)
Ответ на: комментарий от eao197

Давайте считать C объектно-ориентированным языком.

На Си можно писать в объектом стиле (и GObject тому свидетель), но чтобы при этом считать сам Си объектно-ориентированным, не хватает сахара. Если сахар добавить, то получится очередной оо-язык с си-подобным синтаксисом. И что?

Manhunt ★★★★★
()
Ответ на: комментарий от shaiZaigh

тебе тот же вопрос Rust 1.12 (комментарий)

как это сделать правильно?

может реализовать struct и trait OOPObject с таблицей виртуальных функций, плюс макрос(ы) для автоматической регистрации функции в этой таблице?

интересно, что получится, и насколько кудряво или наоборот лаконично

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 1)
Ответ на: комментарий от Manhunt

но чтобы при этом считать сам Си объектно-ориентированным, не хватает сахара.

сахар это не столь важно — сахар можно добавить внешним глупым препроцессором (даже на регекспах)

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

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

Вот так, TraitObject держит таблицу виртуальных функций.
Все вполне естественно и удобно. Я выше кидал ссылку на растбук, там все подробно описано

UPD: в 43 строке благодаря Deref можно просто писать self.name :)

mersinvald ★★★★★
()
Последнее исправление: mersinvald (всего исправлений: 1)
Ответ на: комментарий от Manhunt

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

Методы и сообщения, имхо, концептуально разные вещи. Сообщения юзер диспатчит сам как хочет:

receive
  {foo, A} ->
    foo(A);
  bar ->
    bar(A),
    ok
end

В то время как вызов методов диспатчит рантайм языка (ну или компилятор генерирует код для этого прямо на месте, как в С++).

GoodRiddance
()
Ответ на: комментарий от GoodRiddance

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

Manhunt ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

не хватает того, чтобы компилятор давал по рукам за нарушения

Как насчет pimpl ? Всякие там абстрактные FILE*

Manhunt ★★★★★
()
Последнее исправление: Manhunt (всего исправлений: 1)
Ответ на: комментарий от eao197

Действительно, не понимаю. ООП — это устоявшаяся за 30+ лет троица: инкапсуляция, наследование, полиморфизм. Если это все в языке X из коробки — значит язык X можно назвать ООЯ. Нет — значит нельзя.

прибегнуть к схоластике и доказать, что упомянутая троица — это не ООП.

нет, не так. упомянутая троица — всего лишь один из возможных вариантов, и не столько собственно ООП , сколько некоторой (весьма убогой и ограниченной) объектной модели «как в Simula/Beta/C++».

в других объектных моделях есть более другие наборы фич. например, двоица: «мультидиспетчеризация и мультифорвардинг сообщений» в COS (CLOS для С). или «мультиметоды и метаобъектный протокол» в CLOS. или: «сокрытие реализации и расширяемые записи, полиморфизм модулей» в оберон-2. или «метакласс как first class object и динамическая диспетчеризация сообщений» в смоллток. или «прототипное ООП» в Self, JavaScript.

это всё — разные механизмы (объектные модели) реализации ООП как идеи, концепции, парадигмы «повторно используемых модулей, с first class object новой сущностью (объектом), проверяемой системой типов (этих объектов)»

Если это все в языке X из коробки — значит язык X можно назвать ООЯ. Нет — значит нельзя.

«из коробки» не существенно. ибо в нормальных языках ты сам можешь создавать такие first class object и расширять систему типов.

Если язык не ООЯ, это вовсе не значит, что он плох и что на нем разрабатывать софт сложнее, чем на ООЯ. Это просто значит, что язык не ООЯ. Посему называть его ООЯ или говорить, что он поддерживает ООП просто не правильно.

ООЯ != «ООП как в С++»

«поддерживает ООП» (но допускает без) и «first class object = объекты в ООП» не одно и тоже.

идея != механизм реализации идеи (один из вариантов). но включает, охватывает.

anonymous
()
Ответ на: комментарий от eao197

Я специально характеризовал ООП в этом треде как троицу из наследования, инкапсуляции и полиморфизма. Ибо эти три качества, как показала история и практика, обнаруживаются в (насколько я помню) большинстве ООЯ вне зависимости от того, статически они типизированы или динамически, используются ли в них классы (как в наследниках Simula) или прототипы (как в наследниках Self-а).

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

anonymous
()
Ответ на: комментарий от Manhunt

Соглашусь, но только это _очень_ частный случай. Настолько частный, что для него можно даже сделать нормальный code completion. (Для Ъ-сообщений это где-то в окрестности невозможного).

GoodRiddance
()
Ответ на: комментарий от eao197

В свое время он вбирал идеи из функциональщины (емнип, шаблоны в C++ — это заимствование из ML и Ada), и продолжает это делать.

вот кстати, параметрические пакеты в Ada (параметризованные другими пакетами, например) и сигнатуры/функторы/типы модуля в модульной системе ML — это вот эти высокоуровневые модули как first class objects. «функторы модуля» как интерфейсы в ООП.

В эту же сторону и концепты.

да. см. например в MirageOS Unikernel реализацию драйверов,tcp/ip стека во время компиляции (параметризованных функторами, интерфейсами, концептами, как не назови — идея одна).

anonymous
()
Ответ на: комментарий от tailgunner

Cами интерфейсы - это не ООП, поэтому выражение интерфейсов с помощью ООП (наследования в Си++) неестественно. Но у трейтов конкретно Rust есть операция наследования, и вот это уже можно (при желании) считать ООП.

ну да, интерфейсы более высокоуровневая конструкция, чем механизм её реализации через наследование. например, в Go ad-hoc интерфейсы есть, а наследования нет. в Rust и наследование есть.

межмодульное наследование реализации (а не интерфейсов) — почти всегда плохая идея (ухудшает повторную используемость, повышает связность). значит, модули нужно разбить по-другому.

anonymous
()
Ответ на: комментарий от anonymous

«метакласс как first class object и динамическая диспетчеризация сообщений» в смоллток. или «прототипное ООП» в Self, JavaScript.

Вы батенька читать умеете? Если умеете, то перечитайте тред, я писал и про SmallTalk, и про Self, и про JavaScript. Что характерно, во всех этих языках есть и наследование, и инкапсуляция, и полиморфизм. Только делается по-разному.

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

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Что характерно, во всех этих языках есть и наследование, и инкапсуляция, и полиморфизм. Только делается по-разному.

только никто не считает там основным качеством то же наследование. а считает более другие качества, более гибкие.

наследование как самый жёсткий тип связи «не интересно ни разу».

anonymous
()
Ответ на: комментарий от DarkEld3r

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

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

но да, это уже не С++.

anonymous
()
Ответ на: комментарий от eao197

Так, я не понимаю, каким боком принцип подстановки Лисков относится к нарушению инкапсуляции, например, в этом случае:

в этом случае не относится, т.к. не изменяет инвариант класса и контракты. но если контракты нарушаются, то возникает.

Принцип был сформулирован Барбарой Лисков и Джаннетой Винг в следующем виде: «Пусть q(x) – доказуемое свойство объекта x типа Т. Тогда q(y) должно быть доказуемо для объектов y типа S, где S – подтип типа Т».

Пример из «проектирования по контракту»:
* Предусловия не могут быть усилены в подтипе(потомке)
* Постусловия не могут быть ослаблены в подтипе
* Инварианты супертипа(предка) должны сохранятся для подтипа
* «правило истории»: объекты изменяются только через вызов методов.

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

понятно, что раз в С++ нельзя гарантировать свойства про предусловия, постусловия, инварианты (из-за произвольного расширения), то про «доказуемость свойств» тут всё плохо, и «правило истории» тут не поможет (в отличие от того же Eiffel с проверяемыми в CTFE контрактами и ограничениями контрактов).

это ограничение семантическое, логическое (а не проверяемое компилятором).
в С++ частично можно использовать концепты, но полностью — не поможет(ибо дело добровольное, и расширять можно произвольно).

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

anonymous
()
Ответ на: комментарий от anonymous

Спасибо за ликбез, но мне кажется, что вы о чем-то своем, наболевшем.

И да:

в отличие от того же Eiffel с проверяемыми в CTFE контрактами и ограничениями контрактов

Давно Eiffel научился контракты в компайл-тайм проверять? Или вы это вычитали там же, где и формальное определение принципа подстановки Лисков?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Давно Eiffel научился контракты в компайл-тайм проверять?

подразумевается композиция контрактов предка и потомка через нестрогие or else или and then и явный запрет через строгие операции — правило утверждения переобъявления (assertion redeclaration) (см. книги Б. Мейера), также Принцип динамического связывания

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

(и далее его рассуждения в терминах пред/постусловий и инвариантов), и далее его рассуждения о том, что virtual должен выводить компилятор автоматически, а не писать программист руками, также ковариантность и опорный элемент (like anchor).

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

ковариантность и like anchor например — проверяемы статически. есть ещё ряд ограничений типа «принцип ХХХ», тех же catcalls, и т.п. которые в совокупности и составляют «метод Eiffel» — опять же, для минимизации динамики и проверки статически.

(семантику владения/одалживания в раст, например можно считать тоже одним из таких принципов, составляющих уже «метод Rust», например. другое дело, что в «методе Eiffel» таких принципов довольно много и их не так просто запомнить, в «методе Rust» + HKT эта система правил может быть более «ортогонализированной»)

по идее, здесь нужно что-то вроде типов высшего порядка. и функциональных объектов, вычисляемых во время компиляции. и предикатов типа, с кодировкою вычисляемой CTFE. и каких-то интерфейсов между такими ф.о.,системой типов и модулями. или тех же контрактов над такими вот интерфейсами (трейты, ау) — обеспечивающих composable и compositable.

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

anonymous
()
Ответ на: комментарий от anonymous

Чувак, хватить жить в стране розовых пони и срать радугой. Скачайте себе EiffelStudio, наколбасьте тысяч эдак 20 строк на Eiffel-е и увидите, что все гораздо прозаичнее.

eao197 ★★★★★
()
Ответ на: комментарий от loyd

И да, давайте не будем про friend, пусть о крестов будет хоть какой-то шанс.

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

DarkEld3r ★★★★★
()
Последнее исправление: DarkEld3r (всего исправлений: 1)
Ответ на: комментарий от eao197

Ну тогда расскажите, как же protected нарушает инкапсуляцию?

Сам по себе, не нарушает, но даёт возможность её нарушить. Наследник может сделать public: using A::private_data;.

DarkEld3r ★★★★★
()
Ответ на: комментарий от khrundel

Собственно вся эта пурга с private/protected/friend была добавлена в C++ не от хорошей жизни, а от отсутствия модулей

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от WatchCat

Ну у товаrища такая мулька, что если нету protected, то и нету инкапсуляции.

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

В наследовании интерфейсов. В динамически-типизрованных языках этих заморочек нет. Ваш К.О.

Так себе К.О. Скажем, в Racket интерфейсы есть, наследование интерфейсов тоже, а язык-то динамически типизированный.

Как, например, наличие лямбд не делает C++, Java и C# функциональными языками.

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Скажем, в Racket интерфейсы есть, наследование интерфейсов тоже, а язык-то динамически типизированный.

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

eao197 ★★★★★
()
Ответ на: комментарий от asaw

Ты тут на каждый мой комментарий будешь высказывать своё неповторимое особое мнение?

Почему бы и нет? Тебе же никто не мешает бредить про инкапсуляцию.

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

Ну то есть, возражений нет? И в С++ точно так же можно размазать реализацию, только оно ещё и может внезапно выстрелить благодаря ADL.

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Я под инкапсуляцией понимаю возможность работы с данными только из методов объекта.

Хорошо, тогда разве этого в расте нет?

Если что, мне совершенно не интересно доказывать «полноценность ООП» в расте. А вот с отсутствием инкапсуляции категорически не согласен.

Кстати, не отрицаю, что в С++ всяких «связанных с ООП штук» больше - это очевидно. Другое дело, что отношение к вещам типа protected и friend неоднозначное. Да, оно бывает весьма удобно, но не факт, что в языке с наличием других механизмом оно необходимо. Тут я пока не определился.

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

в языке с нормальным множественным наследованием отдельное понятие интерфейса является лишней (т.е. не нужной) сущностью.

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Хорошо, тогда разве этого в расте нет?

Об этом уже говорилось. Интересная фишка Rust-а в том, что у нас может быть некая структура S и некий трейт T, которые появились независимо друг от друга. И если какому-нибудь Васе Пупкину потребуется использовать экземпляры S там, где ожидается T, то Вася Пупкин может реализовать T для S. Не спрашивая разрешения у авторов S и T.

Это интересная фича.

Но, имхо, пользу она приносит тогда, когда содержимое S открыто. Тогда Вася Пупкин не имеет препятствий для реализации T.

Только вот тогда речи про инкапсуляцию из ООП нет. Тут прямая отсылка к функциональным языкам.

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

eao197 ★★★★★
()
Ответ на: комментарий от eao197

И если какому-нибудь Васе Пупкину потребуется использовать экземпляры S там, где ожидается T, то Вася Пупкин может реализовать T для S. Не спрашивая разрешения у авторов S и T.

Не получится. Раст позволяет имплементить любой интерфейс для типа из текущего модуля, либо интерфейс из текущего модуля для любого типа.

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Не получится. Раст позволяет имплементить любой интерфейс для типа из текущего модуля, либо интерфейс из текущего модуля для любого типа.

Хм... похоже, прежде чем на раст можно будет посмотреть я на пенсию уйду.

anonymous
()
Ответ на: комментарий от DarkEld3r

Ну то есть, возражений нет? И в С++ точно так же можно размазать реализацию, только оно ещё и может внезапно выстрелить благодаря ADL.

В C++ точно так же «размазать» ничего нельзя, потому что поведение определяется в интерфейсе, который ты наследованием не поменяешь. Вы, растаманы, просто органически не отличаете интерфейс от реализации. Кроме того, наследованием ты создаёшь новый тип, что тоже легко отслеживается при желании.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Что «no comment»?
Ты пальцем показать можешь в каком там месте protected-член и доступ к нему?

WatchCat ★★★★★
()
Ответ на: комментарий от tailgunner

приведенный выше трюк с перегрузкой Deref вполне жизнеспособен.

Ну это всё-таки уродство и, как по мне, справедливо считается антипаттерном.

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Это уже совсем другой вопрос.

Зато более важный на практике.

DarkEld3r ★★★★★
()
Ответ на: комментарий от anonymous

так может, контролировать надо не в предке (поставщике интерфейса), а в потомке (потребителе) ?

Дык, я отвечал на претензию в духе «кто-то может реализовать неправильное поведение».

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Исключения можно найти из любого правила.

Вот только, вопреки популярному заблуждению, это не подтверждает правило.

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

Это всё-таки функциональность из коробки, а не сторонняя либа. Ну и возможность «создать какого угодно уродца», как по мне, является плюсом.

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Вот только, вопреки популярному заблуждению, это не подтверждает правило.

SmallTalk, Ruby, Python, Self, JavaScript — нет такой языковой сущности, как интерфейс. Это не правило, это случайность. Я понял.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Вася Пупкин может реализовать T для S. Не спрашивая разрешения у авторов S и T.

Напрямую это сделать нельзя, но да, ограничение можно обойти.

Вот только разве в С++ не точно так же? Можно наследоваться от чужого интерфейса (а трейт - это, в принципе, интерфейс) и одновременно от чужого типа (ну или заменить агрегацией) и подсунуть эту обёртку. Тоже не спрашивая авторов.

Но, имхо, пользу она приносит тогда, когда содержимое S открыто. Тогда Вася Пупкин не имеет препятствий для реализации T.

Не обязательно. Васе может хватить публичного интерфейса S.

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

Вот мне и интересно услышать в чём именно оно проявляется. Отсутствие frient, protected - понятно. «Необходимость» использовать модули, а не классы для сокрытия, как по мне, принципиально ничего не меняет. Что-нибудь ещё?

DarkEld3r ★★★★★
()
Ответ на: комментарий от anonymous

Хм... похоже, прежде чем на раст можно будет посмотреть я на пенсию уйду.

Почему? Тебе кажется, что это слишком сильное ограничение?

Вот только если обращаться к аналогиям, то всё нормально. Возьмём плюсы: в либе A есть интерфейс I, в либе B тип T. Нельзя взять и реализовать интерфейс именно для этого типа (не трогая исходники). Зато можно сделать обёртку:

class W : public A::I, public B::T {
    ...
}
И в расте можно сделать похожим образом.

DarkEld3r ★★★★★
()
Ответ на: комментарий от asaw

В C++ точно так же «размазать» ничего нельзя, потому что поведение определяется в интерфейсе

Ну давай в терминах плюсов. Есть интерфейс Crawler и класс Snail, который от него наследуется. Дальше я делаю FlyingSnail и в реализации метода crawl лечу. Ну прямо как ты показываешь в соседнем примере кода.

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

Вы, растаманы, просто органически не отличаете интерфейс от реализации.

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Вот только разве в С++ не точно так же?

Если S — это struct, а T — это абстрактный класс, то можно. Только вот в общем случае в C++ S не будет структурой. И наружу не будут торчать детали, необходимые для реализации T.

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

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

eao197 ★★★★★
()
Ответ на: комментарий от eao197

SmallTalk, Ruby, Python, Self, JavaScript — нет такой языковой сущности, как интерфейс. Это не правило, это случайность. Я понял.

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

В Pharo (реализация Smalltalk) есть трейты, которые представляют собой как раз интерфейсы.

В Ruby, вроде, совсем ничего такого нет, да. Хотя есть статьи и рукопашные реализации. Даже интересно зачем на это идут программисты на вполне приличном языке, таким же только извращенцы-лисперы должны заниматься?..

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

В JavaScript interface, вроде, является зарезервированным ключевым словом? Может когда-нибудь что-то и добавят на эту тему. (:

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

И наружу не будут торчать детали, необходимые для реализации T.

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

Ну не меняет, ну что тут поделать.

Просто из любопытства: а если взять привычный С++ и заменить public/private на какие-нибудь другие ключевые слова инкапсуляция станет «своеобразной»? А если запретить группировать данные/методы и требовать указания перед каждым? Или пусть доступность зависит от кейса первой буквы поля/функции. Или же сделать так, чтобы это перед классом перечислялось, как-то так:

class C [public: a, b, c, private: d, e] { ... }
В общем, где грань проходит? (:

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

Очевидно, что некий API, за который можно дергать объекты, является интерфейсом. И интерфейс, как логическое понятие, архиважен, т.к. без интерфейса не понятно, как выражать этот самый API.

Однако, речь идет не о понятии, а об отдельной языковой сущности. Надобность в которой есть в статически-типизированных языках (ибо в компайл-тайме нужно проверять допустимость вызова). А вот для динамических, как показывает обширный опыт, — нет. По большому счету duck typing как раз и базируется на том, что нет этих самых интерфейсов как языковых сущностей.

eao197 ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

Видимо, ваше знание Rust-а намного лучше моего. Я, например, полагал, что если в модуле m1 есть структура S, а в модуле m2 — трейт T, то Вася Пупкин в модуле m3 может сделать свою имплементацию T для S.

Мне казалось, что это очень интересная фича. Возможно, одна из killer features языка.

Но, как мне тут указали, я не прав. Вася Пупкин может сделать имплементацию T только в модуле m2. Что, имхо, превращает эту воображаемую фичу в пшик. Но Rust-оманам нравится, так что пусть балуются.

В общем, где грань проходит? (:

У устоявшихся привычках. Как-то так повелось, что в ООЯ единицей инкапсуляции является класс (или объект, если уж речь про прототипные ООЯ). Исключения, конечно же, можно найти. В частности в Java есть package visibitility. Но, в тех ООЯ, с которыми доводилось работать, все-таки инкапсуляция замыкалась на класс/объект. И к этому привыкаешь.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Но Rust-оманам нравится, так что пусть балуются.

Позволь спросить, а если это будет не Rust, то Вася реализует T для S, Петя T для S, Ваня T для S, и т.д.?

Нет уж, пусть лучше владельцы T или S реализуют T для S.

anonymous
()
Ответ на: комментарий от anonymous

Позволь спросить, а если это будет не Rust, то Вася реализует T для S, Петя T для S, Ваня T для S, и т.д.?

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

Нет уж, пусть лучше владельцы T или S реализуют T для S.

Аминь.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

Ладно, ты не понял, объясню ещё раз.

Вася реализовал T для S в пакете A.
Петя реализовал T для S в пакете B.
Ваня реализовал T для S в пакете С.

Ты используешь пакеты A, B и C.

Какую из реализаций ты будешь использовать?

Как указать компилятору, что именно эту реализацию использовать? Может что-то типо?

// crate X
trait T {}

// crate A
impl T(impl_identifier) for S {}

// your crate
use A::impl_identifier;

anonymous
()
Ответ на: комментарий от anonymous

А разве это не решается введением в область видимости только одной реализации из нужного мне пакета?

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

И?

Если есть:

mod C {
  impl A::T for B::S { ... }
}
то я не вижу это определение до тех пор, пока не сделаю use C.

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

Так здесь проблемы нет до тех пор, пока я не создам объект B::S и не попытаюсь привести его к A::T. Вот в этом случае компилятор может сказать, что приведение неоднозначно.

Собственно, в C++ вы можете определить int f(int) в пространствах имен A и B. Если вы сделаете using namespace A и using namespace B одновременно, то не сможете просто вызвать f(0). Вам придется уточнить имя f.

eao197 ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

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

asaw ★★★★★
()
Ответ на: комментарий от eao197

Собственно, в C++ вы можете определить int f(int) в пространствах имен A и B. Если вы сделаете using namespace A и using namespace B одновременно, то не сможете просто вызвать f(0). Вам придется уточнить имя f.

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

Вот представь (просто пример). Вася реализовал Display для String и теперь, тебе в каждом месте использования типа String как Display (println!("{}", string);) прийдётся указывать, что ты хочешь именно реализацию из std...

anonymous
()
Ответ на: комментарий от tailgunner

И в первом же абзаце то, о чём я говорю.

The alternative to coherence is to have some way for users to identify which impls are in scope at any time. It has its own complications;

anonymous
()
Ответ на: комментарий от tailgunner

Пользуясь случаем, хочу спросить:
Можно ли сделать конверсии с помощью Into, From и As[Mut]Ref автоматическими, наподобии того как работает Deref?

Предположим, я хочу определить свой MyString, обернутый вокруг обычной строки, чтобы реализовать для него какие-то сторонние трейты и получить желаемое поведение.
Но если я так сделаю, каждый раз при передаче MyString в функцию, придется вызывать into(), а при приеме — from().
Почему нельзя это вынести как сахар и при взятии ссылки разворачивтаь в as_ref(), при передаче автоматически подставлять into() и при биндинге с аннотацией типа вызывать from()

Искал RFC или хоть какие-то намеки, но ничего не нашел

mersinvald ★★★★★
()
Последнее исправление: mersinvald (всего исправлений: 1)
Ответ на: комментарий от anonymous

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

Если обе реализации одновременно введены в область видимости, то никакая.

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

Только если обе одновременно оказались в области видимости.

прийдётся указывать, что ты хочешь именно реализацию из std...

Если я сделал use для Васиного модуля, то да, придется. Если не сделал, то не придется.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Если я сделал use для Васиного модуля, то да, придется. Если не сделал, то не придется.

Речь идёт не о модуле, а о реализации трейта. Если подключать трейты по use mod;, то я считаю это не правильно, т.к. любая реализация трейта может привести к необходимости правки всех мест где возникают конфликты, то есть я написал impl Display for String и поломал все модули которые используют мой модуль. Если ты хочешь использовать String как Display, то откуда компилятору знать, какой Display использовать? С когерентной системой типов такой проблемы нет, ты просто указываешь use Trait;, но с множественной реализацией так не работает, необходимо указать именно конкретную реализацию.

Если обе реализации одновременно введены в область видимости, то никакая.

Только если обе одновременно оказались в области видимости.

То есть реализация по-умолчанию отсутствует и необходимо явное use impl_T_for_S; для каждой реализации? То есть в каждом крейте у нас будет свой prelude, который надо будет подключать в каждом модуле для использования реализаций трейтов для типов крейта.

use a::{S, Clone_for_S, Display_for_S, Debug_for_S, etc};

anonymous
()
Ответ на: комментарий от eao197

Перечитайте, пожалуйста, то, что я вам отвечал. Там все описано.

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

anonymous
()
Ответ на: комментарий от anonymous

Только если обе одновременно оказались в области видимости.

То есть реализация по-умолчанию отсутствует и необходимо явное use impl_T_for_S; для каждой реализации?

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

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

Как ты будешь вводить реализацию в область видимости?

anonymous
()
Ответ на: комментарий от anonymous

Как ты будешь вводить реализацию в область видимости?

Это не ответ на мой вопрос. Но попробую еще раз.

Есть тип S. Есть трейт T. Есть модуль Vasya с реализацией T для S. Есть модуль Kolya с другой реализацией T для S.

Если я делаю только use Vasya, то вижу только одну реализацию T для S. Она и используется по умолчанию.

Если я делаю только use Kolya, то вижу только одну реализацию T.

Только если я делаю и use Vasya, и use Kolya, то тогда при попытке задействовать T для S возникает неоднозначность, на которую должен указать компилятор.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Это не ответ на мой вопрос. Но попробую еще раз.

Два чая этому господину.

Только если я делаю и use Vasya, и use Kolya, то тогда при попытке задействовать T для S возникает неоднозначность, на которую должен указать компилятор.

Так это то, о чём я и говорил.

любая реализация трейта может привести к необходимости правки всех мест где возникают конфликты, то есть я написал impl Display for String и поломал все модули которые используют мой модуль.

Мне продолжить объяснять на сколько это глупый подход?

anonymous
()
Ответ на: комментарий от anonymous

Мне продолжить объяснять на сколько это глупый подход?

Почему-то вам кажется, что я не понял вашу точку зрения. Однако, я ее давно понял. Просто не считаю ее правильной.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Однако, я ее давно понял.

Однако ранее:

Перечитайте, пожалуйста, то, что я вам отвечал. Там все описано.

----

Просто не считаю ее правильной.

=/

Я тебе говорю, у нас есть проблема, а ты мне, я считаю вашу точку зрения не правильной...

С предложенной тобой моделью, любая, Карл, реализация трейта может поломать код в любом месте.

anonymous
()
Ответ на: комментарий от DarkEld3r

В общем, где грань проходит? (:

Лучше сделать

#define private public

#include «нечто.h»

И дальше лезь куда хочешь.

anonymous
()
Ответ на: комментарий от anonymous

Я тебе говорю, у нас есть проблема, а ты мне, я считаю вашу точку зрения не правильной...

Или вам так кажется.

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

Опять 25...

Очевидно, что проблема непонята. Мне то же. Сильное ощущение, что вы видите то, что другие увидеть не могут. Как называются такие люди с воображаемыми друзьями вы знаете?

anonymous
()
Ответ на: комментарий от anonymous

Товарищ выше предлагает такую концепцию.

// crate t
trait T {
    fn foo(&self) {}
}

// crate s
struct S {}

// crate a
impl t::T for s::S {}

// crate b
impl t::T for s::S {}

// crate c
use a; // неявное использование реализации T

(S {}).foo();

// crate d
use a; // неявное использование реализации T
use b; // неявное использование реализации T, конфликт
use a::T for s::S; // явное использование T из a

(S {}).foo();

Я ему говорю, что реализация любого трейта может вызвать такой конфликт, что означает явное указание реализации. Если кто-то реализует в crate b trait T for S, это может поломать любой др. код, который использует неявное T for S из др. места.

К примеру. Я реализовал Display для String, это будет означать явное указание use std::fmt::Display (или my::Display) for std::string::String в каждом месте, где используется мой код.

anonymous
()
Ответ на: комментарий от anonymous

Если кто-то реализует в crate b trait T for S, это может поломать любой др. код

Вот это непонятно. Почему любой, а не только тот который попробует использовать b?

anonymous
()
Ответ на: комментарий от anonymous

Вот это непонятно. Почему любой, а не только тот который попробует использовать b?

Я это и имею в виду.

anonymous
()
Ответ на: комментарий от anonymous

Я это и имею в виду.

Похоже ваш друг и в самом деле воображаемый. :)

anonymous
()
Ответ на: комментарий от eao197

Что, имхо, превращает эту воображаемую фичу в пшик.

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

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

Впрочем, доступа к приватным данным всё равно не будет.

DarkEld3r ★★★★★
()
Ответ на: комментарий от anonymous

Почему любой, а не только тот который попробует использовать b?

Вот я тоже не понял, почему аноним говорит про поломку любого кода.

eao197 ★★★★★
()
Ответ на: комментарий от asaw

Это не одно и тоже, что прилепить новый интерфейс к тому же самому объекту.

Ну давай ещё раз. Есть либа с интерфейсом «ползти» и типом «улитка». И в С++ и в расте к приватным данным обратиться будет нельзя. Как по мне, инкапсуляция не нарушается, но идём дальше.

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

Передать летающую_улитку по базовому интерфейсу в С++ можно, но если та сторона ничего не знает, то ничего не случится. Аналогично в расте - трейт надо импортировать явно. И, как по мне, это ничем не отличается от явного использования функции типа fly(Snail&) в С++. Ну или создания обёртки и использования её методов. Или динамик каста к нужному интерфейсу, раз уж мы о нём знаем.

DarkEld3r ★★★★★
()
Ответ на: комментарий от anonymous

Вася реализовал Display для String и теперь, тебе в каждом месте использования типа String как Display (println!(«{}», string);) прийдётся указывать, что ты хочешь именно реализацию из std...

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от anonymous

Мне продолжить объяснять на сколько это глупый подход?

Попробуй объяснить мне.

то есть я написал impl Display for String и поломал все модули которые используют мой модуль.

Почему? Ведь реализации (кроме «дефолтных» из prelude) надо каждый раз явно импортировать. То есть, у тебя будет использоваться своя реализация Display для строк, но у тех, кто использует твою либу, если они явно не захотели твою реализацию, будет использоваться дефолтная.

Или в чём проблема?

DarkEld3r ★★★★★
()
Ответ на: комментарий от anonymous

И дальше лезь куда хочешь.

Справедливости ради, это UB (в общем случае).

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

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

anonymous
()
Ответ на: комментарий от DarkEld3r

Почему? Ведь реализации (кроме «дефолтных» из prelude) надо каждый раз явно импортировать. То есть, у тебя будет использоваться своя реализация Display для строк, но у тех, кто использует твою либу, если они явно не захотели твою реализацию, будет использоваться дефолтная.

Вот и я о том же, что нужно как-то идентифицировать реализации в такой модели, но тот что выше, говорит о неявном использовании при использовании модуля, а не импорте реализации (как я предлагал выше).

anonymous
()
Ответ на: комментарий от anonymous

а не импорте реализации (как я предлагал выше).

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

DarkEld3r ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.