Comments 22
"Полиморфизм - это принцип в программирование, который позволяет нам писать гибкий, масштабируемый и поддерживаемый код. "
Правильно я понял, что без этого принципа невозможно писать гибкий, масштабируемый и поддерживаемый код?
Странно, а как раньше до придумывания такого принципа писали такой код?
------------------
Полагаю, что следующее определение более приемлемо:
"Полиморфизм (polymorphism) — это понятие из объектно-ориентированного программирования, которое позволяет разным сущностям выполнять одни и те же действия. При этом неважно, как эти сущности устроены внутри и чем они различаются. "https://blog.skillfactory.ru/glossary/polimorfizm/
К какой-то из статей, посвященной "замечательному ООП" (там еще был пример с калькулятором и арифметическими действиями) я писал комментарий, где все тоже самое реализовывалось просто на процедурах, безо всякого ООП. И при этом было точно также масштабируемо.
Более того, там вся инициализация происходила на этапе компиляции. И никаких вызовов конструкторов в рантайме. Т.е. еще и накладных расходов меньше.
Я не к тому, что ООП плохо. А к тому, что демонстрировать все это нужно на более сложных примерах. А не взятых из брошюры "ООП для совсем дурачков".
А чем вам полиморфизм в функциональных языках не угодил и в целом принцип полиморфизма, который появился задолго до ООП? И в целом да, без него очень сложно получить хороший код, ибо tight coupling и вы не можете использовать абстракции.
А кто говорит про угодил?
В статье дано вольное (авторское) определение этого понятия.
Согласно вики:
Полиморфизм в языках программирования и теории типов — способность функции обрабатывать данные разных типов[1][2][3].
Получается, что его нет ни в фортарне, ни в СИ. Все библиотеки линейной алгебры в пакетах ИИ написаны на фортране.
CИ и теперь живее всех живых.
И знаете, как-то никто не жалуется, что нет полиморфизма в этих языках. Что не так?
Заголовочные файлы в Си как абстракция с возможностью как статически, так и динамически подлинковывать разные реализации и есть полиморфизм. По первой же вашей ссылке в вики есть цитата Страуструпа с релевантным определением. А он немножко разбирается а Си. Ну и как вам перспективы без полиморфизма?
Вы пока ничего конкретного не написали про СИ. Заголовочные файлы не являются функциями.
Если Вы утверждаете обратное, то дайте определение полиморфизма. Иначе у Вас все обзывается этим словом.
Прекрасно пишу без полиморфизма на C ( т е не пишу одну функцию для разных типов а пишу столько функций сколько типов).
Можете привести пример кода на СИ с полиморфизмом?
Заголовочные файлы, как правило, содержат сигнатуры функций. Которые, в свою очередь являются абстракциями, реализации которых линкуются статически или динамически. Это знает любой человек, который написал хотя бы примитивный "Hello, world!" на Си. Вы инклудите stdio.h, который содержит абстракцию printf, но не реализацию. Поскольку понятия не имеете, какая именно будет. Windows или Linux, glibc или musl, версии 2.1.2 или 2.1.3 . Вы учили меня читать Википедию, но сами не удосужились ознакомиться с одной из ключевых статей про полиморфизм в части статического и динамического полиморфизмов.
Вы сослались ранее на цитату Страуструпа , но очевидно не дочитали ее.
Читаем вместе и внимательно:
Широко распространено определение полиморфизма, приписываемое Бьёрну Страуструпу: «один интерфейс (как перечень объявлений) — много реализаций (определений, связываемых с этими объявлениями)»[6], но под это определение подпадает лишь ad-hoc-полиморфизм (мнимый полиморфизм).
А ваше рассуждение о том, что все знают и пишут примитивные программы с полиморфизмом , это Ваше личное мнение, по-моему - ошибочное.
Кроме того, в определениях функций в заголовочных файлах явно указывается тип параметров и поэтому полиморфизм здесь не катит.
Вот нашел на CИ пример:
В языке Си функции не являются объектами первого класса, но возможно определение указателей на функции, что позволяет строить функции высших порядков[96]. Также доступен небезопасный параметрический полиморфизм за счёт явной передачи необходимых свойств типа через бестиповое подмножество языка, представленное нетипизированным указателем void*
[97] (называемым в сообществе языка «обобщённым указателем» (англ. generic pointer). Назначение и удаление информации о типе при приведении типа к void*
и обратно не является ad-hoc-полиморфизмом, так как не меняет представление указателя, однако, его записывают явно для обхода системы типов компилятора[96].
Например, стандартная функция qsort
способна обрабатывать массивы элементов любого типа, для которого определена функция сравнения[96].
про си вопрос снимаем но про фортран оставляем.
-------------------
Дополню свой ответ:
Исходя из этого примера, те кто пишет примитивные программы на СИ не используют полиморфизм. Они даже не понимают, что это такое.
Ну так библиотеки линейной алгебры и не являются примером хорошего расширяемого кода. Примером хорошего оптимизированного кода - да, являются (не проверял, но надеюсь!), но вот расширяемого - точно нет.
Кстати, насчёт того, что полиморфизма нет в Си, вы несколько погорячились. Комбинация из слабой типизации, прямого доступа к памяти и костылей способна заменить отсутствующие языковые механизмы. Вот вам первый попавшийся пример: тип данных struct sockaddr *
и полиморфная функция connect
"Правильно я понял, что без этого принципа невозможно писать гибкий, масштабируемый и поддерживаемый код?"
Претензия уровня "автомобиль позволяет быстро передвигаться? Враньё: быстро передвигаться можно и без автомобиля!"
Это определение тоже не очень корректно. Полиморфизм - такое поведение кода, когда один и тот же код (например, shape.show() )по-разному реализует одну и ту же абстракцию поведения ( show() )в зависимости от типа вызвавшего его экземпляра (в объектном программировании - объекта, в данном примере объекта-фигуры, на которого ссылается объектная переменная shape ).
По-моему суть полиморфизма в возможности записать одну функцию и использовать ее для разных типов данных. При этом для пользователя эта функция - черный ящик, т е не важно какой у нее внутри код. Полиморфизм облегчает жизнь программисту при написании программы, но при этом не факт эффективности исполнения таких функций, так как разбор типов входных параметров и выбор кода исполнения делается внутри функции.
Полагаю, что хорошим примером полиморфизма являются все скриптовые языки.
Так все-таки, что такое Trait и что с его помощью можно сделать такого, что невозможно, например, с абстрактным базовыми классами на том же С++?
Через трейты можно расширять поведение уже существующих типов, код объявления которых ты не контролируешь. В том числе примитивов. И за это платишь (передаёшь vtable pointer) только в тех местах, где по факту используется dynamic dispatch. А в С++ dynamic dispatch бывает только у классов. Причём класс будет реализовывать только закрытый набор абстрактных классов, от которых автор класса унаследовался, объявляя его. Причём инстансы всегда будут таскать в себе vtable pointer, даже если используются только мономорфно.
К тому же, трейты можно использовать и для dynamic dispatch (абстрактный базовый класс в C++), и как type constraint для дженериков (Concepts из C++20). Очень удобно, что не требуется объявлять две этих отдельных сущности, как в C++. И это позволяет при неоходимости превратить любую шаблонную функцию в не-шаблонную, принимающую динамический trait object. Например, если хочется избавиться от пробрасывания type parameter для шаблона, ускорить компиляцию или уменьшить размер бинарника.
Я мог перечислить не все нюансы, можете дополнительно загуглить, чем traits и typeclasses (похожее понятие из Haskell) отличаются от OOP interfaces.
Звучит конечно круто, что можно расширять поведение других структур. Но тут нужно сделать ремарку, что, к сожалению, нельзя имплементить сторонние трейты для сторонних структур, что бывает весьма обидно. Отсюда появляются всякие фиче-тоглы у популярных либ для интеграции с другими, типа как у serde есть фича для работы с uuid.
Обидно бывает, но это всё равно уже более гибкая ситуация, чем в плюсах, где для сторонних типов нельзя реализовать вообще никакой новый интерфейс. В этом посте хорошо описаны причины ограничений и возможные альтернативы.
struct Foo {}
struct Bar {}
trait Ctor<T> { fn make() -> T; }
enum Foobar {}
impl Ctor<Foo> for Foobar {
fn make() -> Foo { Foo {} }
}
impl Ctor<Bar> for Foobar {
fn make() -> Bar { Bar {} }
}
fn main() {
let _: Foo = Foobar::make();
let _: Bar = Foobar::make();
}
Как будет выглядеть переопределение/перегрузка статик функции на C++?
Выше про примитивы рассказали, но не показали. А ваш с++ может так?
trait Hello {
fn hello(&self);
}
impl Hello for i32 {
fn hello(&self) {
if *self == 42 {
println!("число {self} = 42");
} else {
println!("число {self} != 42");
}
}
}
fn main() {
let i = 42;
i.hello();
}
Да, накладываются большие ограничения на реализацию трейтов (типажей), реализация может быть написана только для своего трейта или для своего типа, но нельзя взять одновременно чужой трейт и чужой тип - не скопилируется. Раньше, вроде, обходилось путём typedef - и у тебя на руках якобы свой тип.
Чуть-чуть прикопаюсь, newtype pattern некорректно сравнивать с typedef. Первое это новый тип-обёртка, а второе это просто алиас, аналог слова type
в расте.
Ну и в примере стоило бы написать чуть более сложный/обобщённый код для использования этого трейта. А то в текущем виде от трейта нет никакой выгоды и проще написать свободную функцию, что и на плюсах можно сделать.
Для упрощения варианта с Enum можно воспользоваться прекрасным крейтом enum_dispatch.
Trait-объекты и полиморфизм в Rust