Синтаксический сахар¶
— это синтаксис, который позволяет разработчикам писать код проще, «сладким» способом. Следовательно, такой способ даёт роскошь не знать, как работает система под капотом. Синтаксический сахар связан не только с Python, подробнее по ссылке.
Раннее уже были изучены некоторые способы использования синтаксического сахара в Python, такие как:
Важно понимать и другие варианты применения такого функционала изучаемого языка.
Магические методы (Magic Methods)¶
(иногда их ещё называют специальными методами) — это методы с предопределенными именами, характеризующимися двойным подчеркиванием в начале и в конце — __init__ как пример. Они являются «магическими», потому что
- эти методы вызываются косвенно
- не нужно вызывать напрямую
- все делается за кулисами
Например, когда создаётся экземпляр класса x = MyClass() , Python выполнит необходимые вызовы __new__ и __init__ .
Пример¶
Как видно из примера, метод __repr__() тесно связан с известной функцией print() . Вот еще несколько примеров:
В этом разделе не будут подробно рассматриваться магические методы, если есть желание узнать больше о них — можно заглянуть в документацию (как пример для Python версии 3.11).
Являются ли магические методы синтаксическим сахаром?
Генератор словарей (dict-comprehension)¶
Операторы присваивания¶
Операторы сравнения¶
Тернарный оператор¶
Большие числа¶
Для удобства чтения большие числа можно записывать с использованием символа нижнего подчёркивания.
Многоточие (Ellipsis)¶
Python имеет специальный встроенный одноэлементный объект под названием Ellipsis . Если вывести три точки или слово Ellipsis в интерактивной оболочке Python, результаты будут следующими:
Этот простой предмет кажется незаметным, но если правильно его использовать, он может облегчить нашу жизнь. Рассмотрим три общих сценария, в которых можно использовать Ellipsis .
Ellipsis — это заполнитель для ненаписанного кода¶
При разработке нового модуля обычно определяются некоторые функции или классы, но не реализуются сразу. Потому что хочется только определить, что нужно написать в будущем, и не слишком заботимся о деталях реализации на раннем этапе. В таком сценарии Ellipsis — наш лучший друг:
Можно просто использовать Ellipsis в качестве заполнителя для функции или класса использую тождественный . pass .
Вообще говоря, хорошей практикой программирования является то, что сначала проектируются необходимые функции или классы, а потом реализуются. Поэтому такой способ может помочь нашему уму ясно видеть всю структуру и не зацикливаться сразу на деталях.
Использование Ellipsis в NumPy , чтобы опустить размеры¶
NumPy — важная библиотека Python для Data Science. Ellipsis полезно при работе с многомерными массивами в NumPy .
Например, если есть трёхмерная матрица и необходимо разделить её, то есть как минимум три способа как сделать это:
Использование Ellipsis (трёх точек) является наиболее экономичным способом разделения многомерной матрицы. Потому что это требует наименьшего набора текста. Время программистов — деньги, не так ли?
Использование Ellipsis для подсказки типа¶
Подсказки типов были новым функционалом Python версии 3.5. На основе PEP 484 Ellipsis имеет особое значение для этого функционала.
С одной стороны однородные кортежи произвольной длины могут быть выражены с помощью одного типа и Ellipsis , например, Tuple[int, . ] .
С другой стороны можно объявить возвращаемый тип вызываемого объекта без указания сигнатуры вызова, заменив Ellipsis (три точки) на список аргументов:
Ellipsis — интересный синтаксический сахар в Python. Его удобно использовать в некоторых сценариях.
Морж-оператор (walrus-operator)¶
Каждая новая версия Python добавляет в язык новые функции. Для Python 3.8 самым большим изменением было добавление выражений присваивания. В частности, оператор := дает новый синтаксис для присваивания переменных в середине выражений. Такой оператор в просторечии известен как морж-оператор.
Уже было упоминали ранее о существовании оператора := , полезно погрузиться в дополнительные возможности такого оператора.
Поговорим о том, как:
- определить морж-оператора и понять его значение
- понимать варианты использования морж-оператора
- избегать повторяющегося кода с помощью морж-оператора
- конвертировать код, использующий морж-оператора, в код, использующий другие методы присваивания и наоборот
- понимать влияния на обратную совместимость при использовании морж-оператора
- использовать соответствующий стиль в выражениях присваивания
Для корректной работы морж-оператора требуется Python 3.8 или более поздней версии.
Основы морж-оператора¶
Оператор := официально известен как оператор выражения присваивания. Во время ранних дискуссий его назвали оператором моржа, потому что синтаксис := напоминает глаза и бивни лежащего на боку моржа. Также иногда можно увидеть, что оператор := называют «оператор двоеточие равно» (colon equals operator). Еще одним термином, используемым для выражений присваивания, являются именованные выражения.
Чтобы получить первое представление о том, что такое выражения присваивания, можно поэкспериментировать со следующим кодом:
В примере выше показан традиционный оператор присваивания, в котором переменной walrus присваивается значение False .
Затем, используется выражение присваивания, чтобы присвоить значение True для walrus . В обоих случаях можно ссылаться на присвоенные значения, используя имя переменной walrus .
Между двумя типами присваиваний, рассмотренными выше с переменной walrus , есть тонкая, но важная разница. Выражение присваивания возвращает значение, а традиционное присваивание — нет.
В этих примерах можно увидеть еще один важный аспект операторов моржа. Оператор := не делает ничего такого, что было бы невозможно без него. С помощью оператора моржа можно лишь делать определенные конструкции более удобными и иногда можно более четко передать цель кода.
Общее представление о том, что такое оператор := и что он может делать получено. Это оператор, используемый в выражениях присваивания, который может возвращать присваиваемое значение, в отличие от традиционных операторов присваивания.
Один из принципов разработки, лежащий в основе оператора walrus, заключается в том, что не существует идентичных контекстов кода, в которых были бы допустимы как оператор присваивания, использующий оператор = , так и выражение присваивания, использующее оператор := . Например, невозможно выполнить простое присваивание с помощью оператора walrus:
В большинстве случаев можно добавить круглые скобки () вокруг выражения присваивания, чтобы сделать код валидным:
Однако такая конструкция не имеет большого смысла, т.к. удобнее воспользоваться оператором = . При этом запись традиционного оператора присваивания = внутри таких скобок не допускается.
Варианты использования морж-оператора¶
Рассмотрим несколько примеров, где морж-оператора может упростить код. Общая тема всех этих примеров заключается в том, чтобы избегать различных видов повторения:
- повторные вызовы функций могут сделать код медленнее
- повторяющиеся операторы могут затруднить сопровождение кода
- повторяющиеся вызовы итераторов могут сделать код чрезмерно сложным
- увидим, как морж-оператор может помочь в каждой из этих ситуаций
Отладка¶
Возможно, один из лучших вариантов использования морж-оператора — отладка сложных выражений. Допустим, стоит задача найти расстояние между двумя точками на земной поверхности. Один из способов сделать это — использовать формулу гаверсинуса :
где \(\varphi\) представляет широту, а \(\lambda\) — долготу каждого местоположения.
Чтобы продемонстрировать эту формулу, можно рассчитать расстояние между Москвой (55,45° с. ш., 37,36° в. д.) и Ванкувером (51,3° с. ш., 0,7° з. д.) следующим образом:
Как можно увидеть, расстояние от Москвы до Лондона составляет чуть более 2500 километров.
Теперь предположим, что нужно перепроверить реализацию и посмотреть, насколько члены гаверсинуса влияют на конечный результат. Можно скопировать и вставить часть из основного кода, чтобы оценить его отдельно. Однако также можно использовать оператор := , чтобы дать имя интересующему подвыражению:
Преимущество использования морж-оператора здесь заключается в том, что одновременно можно вычислять значение итогового значения и отслеживаете значение ϕ_hav . Это позволяет подтвердить, что не было допущено никаких ошибок во время отладки.
Списки и словари¶
Списки — это мощные структуры данных в Python, которые часто представляют собой ряд связанных атрибутов. Точно так же словари используются во всем Python и отлично подходят для структурирования информации.
Иногда при настройке таких структур данных приходится выполнять одну и ту же операцию несколько раз. В качестве первого примера вычислим некоторую базовую описательную статистику списка чисел и сохраним их в словаре:
Важно обратить внимание, что и сумма, и длина списка чисел вычисляются дважды. В этом простом примере последствия не так уж плохи, но если бы список был больше или вычисления были бы более сложными, вероятно захотелось бы оптимизировать такой код. Для этого можно сначала переместить вызовы функций из определения словаря:
Переменные num_length и num_sum используются только для оптимизации вычислений внутри словаря. Используя морж-оператор, эту роль можно сделать более понятной:
num_length и num_sum теперь определены внутри определения описания. Это явный намёк на то, что эти переменные используются только для оптимизации этих вычислений и больше не используются позже.
Область видимости переменных num_length и num_sum одинакова в примере с морж-оператором и в примере без него. Это означает, что в обоих примерах переменные доступны после определения описания.
Несмотря на то, что оба примера функционально очень похожи, преимущество использования выражений присваивания заключается в том, что оператор := сообщает о назначении этих переменных как одноразовые оптимизации.
f-строки (f-strings)¶
О f-строках говорили ранее, полезно погрузиться в дополнительные возможности этого удобного и в последнее время наиболее часто используемого функционала для форматирования строк.
В большинстве случаев синтаксис аналогичен старому % -форматированию с добавлением <> и : вместо % . Например, %03.2f можно перевести как <:03.2f>.
Спецификаторы формата¶
Спецификаторы в f-строках можно использовать таким образом:
Спецификаторы формата могут содержать оцениваемые выражения. Это позволяет использовать следующий код:
После оценки выражений в спецификаторе формата (при необходимости) спецификаторы формата не интерпретируются оценщиком f-строки. Как и в str.format(), они просто передаются в метод format() форматируемого объекта.
Переменные со значениями¶
Версия Python 3.8 не обошла стороной и f-строки, дополнив их возможностями по выводу имён переменных вместе со значениями.
Синтаксический сахар
Синтаксический сахар (англ. syntactic sugar ) — термин, обозначающий дополнения синтаксиса языка программирования, которые не добавляют новых возможностей, а делают использование языка более удобным для человека.
Содержание
Определение
«Синтаксический сахар» — это любой элемент синтаксиса языка программирования, который даёт программисту альтернативный способ записи другой, уже имеющейся в языке синтаксической конструкции, и при этом является более удобным, или более кратким, или похожим на другой распространённый способ записи, или помогает писать программы в хорошем стиле. С формальной точки зрения синтаксический сахар ничего не меняет и выразительности языку не прибавляет, однако может заметно облегчить программисту описание некоторых операций. Одновременно синтаксический сахар, особенно при его неумеренном применении, может ухудшать читаемость кода и усложнять его поддержку. Конструкции, являющиеся синтаксическим сахаром, могут легко транслироваться в конструкции основного синтаксиса.
Необходимо отметить, что понятие синтаксического сахара во многом условно. Его использование предполагает, что из всего множества синтаксических конструкций можно выделить некоторый «базовый набор», обеспечивающий всю функциональность языка, и тогда дополнительные синтаксические средства, которые при желании можно выразить с помощью базового набора, и будут для данного языка синтаксическим сахаром. Однако многие конструкции являются взаимозаменяемыми, и далеко не всегда можно определённо сказать, какие именно из них являются базовыми, а какие — дополнительными. Например, в языке Модула-2 есть четыре вида циклов: цикл с предусловием, цикл с постусловием, цикл с шагом и безусловный цикл. Теоретически, первые три вида циклов могут быть легко выражены через последний. Являются ли они, в таком случае, синтаксическим сахаром? Обычно так не говорят, хотя формально под вышеприведённое определение они попадают.
Примеры
Массивы в Си
Массивы в Си представляют собой блоки в памяти. Доступ к элементам массива производится через указатель на начало блока памяти (то есть, на начало массива) и смещение элемента относительно начального адреса. Это может быть записано без использования специального синтаксиса для массивов ( a — указатель на начало массива, i — индекс необходимого элемента, size — размер элемента данного типа в памяти): *(a + i * size) , но непосредственные операции с адресами в памяти и смещениями являются большим источником ошибок программистов, поэтому язык предоставляет специальный синтаксис: a[i] . Кроме того, для массивов с size = 1 есть возможность обратиться к i -му элементу массива уж совсем экзотическим способом: i[a] , что аналогично a[i] , так как значение указателя i+a , очевидно, равно a+i .
Тернарная операция в Си
Другой известный пример специализированной языковой конструкции — тернарная условная операция языка Си ?: . Следующие два фрагмента кода делают одно и тоже:
Причина введения такой операции — желание вставлять проверку простых условий прямо в выражения и возможность прямо указать компилятору, что результатом проверки условия будет единственное значение. Конструкция действительно сокращает запись, но вот по поводу её удобства мнения могут быть разными. Многие считают, что сокращение записи в данном случае не окупает ухудшение читаемости кода.
Переопределение операторов
К синтаксическому сахару можно отнести и переопределение операторов, поддерживаемое многими языками программирования. В принципе, любая операция может быть оформлена как процедура (функция, метод). Переопределение операторов позволяет выполнять операции, созданные программистом, внешне так же, как и встроенные в язык.
Свойства
Ещё одним примером синтаксического сахара является концепция «свойств», поддерживаемая многими современными языками программирования. Имеется в виду объявление в классе псевдополей, которые внешне ведут себя как поля класса (имеют имя, тип, допускают присваивание и чтение), но в действительности таковыми не являются. Каждое обращение к свойству преобразуется компилятором в вызов метода доступа. Свойства совершенно не являются необходимыми (методы доступа можно вызывать и непосредственно) и используются исключительно для удобства, поскольку код с использованием свойств выглядит несколько проще и понятнее.
Критика
Не все программисты считают наличие синтаксического сахара в языках программирования и использование его программистами благом. Известна точка зрения Никлауса Вирта, которую разделяет часть программистского сообщества: согласно ей, любое расширение языка, не вызванное необходимостью, ухудшает его, так как приводит к усложнению транслятора и, соответственно, к понижению его надёжности и производительности. Одновременно возрастает сложность изучения языка и сложность сопровождения программ. Кроме того, сам факт наличия дополнительных синтаксических средств часто играет провоцирующую роль: он побуждает программиста прибегать к различным синтаксическим трюкам вместо того, чтобы глубже анализировать задачу и реализовывать более эффективные алгоритмы. Эти взгляды отразились в языках семейства Оберон, очень простых и практически лишённых синтаксического сахара.
Известен афоризм Алана Перлиса: «Синтаксический сахар вызывает рак точек с запятой». Точка с запятой («;»), являясь обязательной частью большинства популярных языков программирования, даже если в новом языке бесполезна, оставляется как необязательный элемент, так как большинство программистов имеют прочную привычку её использования. В оригинале афоризм обыгрывает созвучие английских слов semicolon (точка с запятой) и colon, последнее из которых означает не только двоеточие, но и толстый кишечник (colon cancer — рак толстого кишечника).
Чаще критика направляется на отдельные, часто встречающиеся виды синтаксического сахара: переопределение операций, свойства, сложные операции (вроде вышеупомянутой тернарной операции ?: в Си). Доводы критиков, в основном, сводятся к тому, что подобные средства, в действительности, не делают программу ни проще, ни понятнее, ни эффективнее, ни короче, но приводят к дополнительной трате ресурсов и усложняют восприятие, а значит и сопровождение программы.
Синтаксическая соль
В противоположность «синтаксическому сахару» в понятие «синтаксическая соль» (англ. syntactic salt ) [1] на жаргоне хакеров обозначает конструкции в языке программирования, которые необходимо употреблять при выполнении потенциально небезопасных действий. Таким образом, используя их, программист подтверждает, что сомнительное действие предпринято им сознательно, а не является случайной ошибкой или результатом непонимания. Так же, как «синтаксический сахар» не добавляет языку выразительности, «синтаксическая соль» не расширяет возможности языка и не нужна транслятору для корректной компиляции программы; она предназначена исключительно для людей, пользующихся данным языком. Классическим примером общеизвестной и широко применяемой «синтаксической соли» являются имеющиеся почти в любом языке со статической типизацией встроенные команды преобразования типов данных. Формально эти команды излишни (что доказывает классический язык Си, где любое преобразование типов допустимо и выполняется автоматически), но в языках, где их применение обязательно, программист вынужден каждый раз обращать внимание на то, что он выполняет потенциально опасное смешение типов (которое часто указывает на логическую ошибку в программе). В зависимости от строгости языка программирования использование «синтаксической соли» может быть обязательным или факультативным. В первом случае транслятор воспринимает её отсутствие как синтаксическую ошибку, во втором — выдаёт при трансляции предупреждение, которое программист может проигнорировать. В отличие от «синтаксического сахара», который расширяет свободу выражения программиста, «синтаксическая соль» её сужает, требуя «без причины» писать длинные конструкции.
В Jargon File написано: «синтаксическая соль вредна, поскольку повышает артериальное давление хакера». Действительно, при написании небольших программ, создаваемых и поддерживаемых одним человеком, предосторожности могут показаться излишними, однако при промышленной разработке крупных программных комплексов, поддерживаемых большими коллективами программистов (зачастую, к тому же, не самой высокой квалификации), «синтаксическая соль» помогает не ошибаться в разработке и эффективнее разбираться в коде, написанном другими разработчиками.
Что означает разговорное выражение «синтаксический сахар»?
В видео-уроках встретилось выражение у автора синтаксический сахар, что оно означает?
![]()
Словосочетание «синтаксический сахар» используется для описания синтаксических конструкций, которые вводятся только для упрощения реализации чего-либо (сокращения объема кода, повышения читаемости и т.д.) в том или ином языке.
При этом, без синтаксического сахара вполне можно обойтись, но реализация без его использования получится более громоздкой (сложной, непонятной, . ).
Типичный пример — новый синтаксис для стрелочных функций в ES6:
В ES5 эту конструкцию можно записать так:
Для любителей JS отмечу, что стрелочные функции еще и контекст вызова «привязывают» автоматически, но в данном конкретном примере это не существенно.
Синтаксический сахар — конструкция языка, которая полностью дублирует уже имеющиеся возможности, но при этом обладает преимуществом в удобстве/краткости/похожести/стилистике.
В данном случае «тернарный оператор if» полностью совпадает с типичным if-else и присваиванием, но немного короче.
аналогично, но короче чем
![]()
Читаю книгу "Вы не знаете JS: This и Прототипы Объектов. Глава 4: Смешивая объекты "классов"" И наткнулся на ваш вопрос. Вот небольшой пример JS и сахара, который вы еще не раз застанете и будете возмущенны, если привыкли к ООП и классам в других языках.
Но значит ли это, что в JavaScript действительно есть классы? Строго и однозначно: Нет.
Поскольку классы являются шаблоном проектирования, вы можете, приложив немало усилий (как мы увидим далее в этой главе), реализовать некоторое приближение для большей части классической функциональности класса. JS пытается удовлетворить чрезвычайно распространенное желание проектировать с классами, предоставляя, казалось бы, похожий на классы синтаксис.
Хотя у нас и может быть синтаксис, похожий на классы, это больше похоже на то, что механика JavaScript борется против того, чтобы вы использовали шаблон проектирования class. Так как под капотом механизмы, которые вы строите, работают совсем по-другому. Синтаксический сахар и (очень широко используемые) JS библиотеки для работы с "классами" проходят долгий путь скрывая эту реальность от вас, но рано или поздно вы столкнетесь с тем, что классы которые у вас есть в других языках не похожи на фейковые "классы", которые мы создаем себе в JS.
База по языкам программирования: Синтаксический сахар или история развития языков
Продолжаю выкладывать выдержки из вводного курса нашей компании по промышленному программированию.
Часть третья: Синтаксический сахар или история развития языков
В данной части расказывается история развития языков программирования, а так же доступно объясняется что такое ООП и функциональное программирование. Другие части можно найти тут.
Синтаксический сахар (syntactic sugar) — общее обозначение дополнений к синтаксису ЯП, которые делают использование языка более удобным, но не добавляют ему новых возможностей.
Вся история развития ЯП — это история повышения сладости синтаксического сахара.
Машинные языки
Всё началось с машинно-зависимых языков — языков, учитывающий структуру и характеристики определённых компьютерных платформ. Те, кто программировал на калькуляторах помнят, как составлялись на них программы.
Десяток регистров, куда записывались результаты вычислений (где они, эти гигабайты оперативки?), пара регистров смещения (вспомните машину Тьюринга, да-да, регистры обозначали из какого регистра данных брать следующую команду!), и регистр команды, куда нужно было записать очередную операцию (прочитать значение, записать значение, сложить значение двух регистров памяти и т.д.).
Архитектура фон Неймана
Даже архитектура этих устройств не всегда соответствовала архитектуре фон Неймана — стандартной для современных компьютеров.
Собственно, архитектура фон Неймана подразумевает отделение памяти от процессора и хранение в памяти изменяемых программ. Калькуляторы же обычно являлись устройствами с фиксированным набором выполняемых программ.
Собственно, переход к архитектуре фон Неймана породил возможность задавать автоматически выполняющиеся программы из внешнего источника — поначалу из перфолент и перфокарт.
Люди так и программировали, пробивая отверстие в карточке, соответствующее определённому регистру, таким образом, побитово задавая значения в них. Много историй связано с тем, как программа, набитая на сотнях перфокарт в буквальном смысле этого слова рассыпалась, когда неуклюжие техники роняли эти стопки картона на пол.
Ассемблер
На машинных кодах программировать было не очень удобно, по этому при первой же возможности появился ассемблер — язык, повторяющий машинные операции, но с человекопонятными командами и возможностью ручкой по бумаге описать алгоритм не как набор битов, а как какой-то более осмысленный текст.
Ассемблер так же привязан к архитектуре машины (поскольку его команды повторяют команды процессора), но шаг в пропасть был уже сделан и языки начинали всё больше и больше обрастать кристаллами сахара.
Стековые языки
Первой ласточкой стало использование стеков данных. Стек появился для решения задачи временного хранения произвольных данных. Конечно, данные можно сохранять и в регистре, однако в этом случае нужно помнить имя каждого регистра, данные из которого хочется получить.
Характерностью стека является особый порядок получения из него данных: в любой момент времени в стеке доступен только верхний элемент, т.е. элемент, загруженный в стек последним. Выгрузка из стека верхнего элемента делает доступным следующий элемент, по аналогии с автоматным рожком — первый засунутый туда патрон достать можно только последним.
Сейчас это может казаться дико неудобным, но это позволило создавать подпрограммы.
Перед вызовом подпрограммы, мы заполняем специально именованный стек данными. Подпрограмма, зная, в каком порядке помещены в стек параметры, может забрать их оттуда и использовать при своем выполнении, а по выполнении поместить результаты своего труда в тот же или в другой стек. Кроме того, основная программа имеет возможность сохранить свои данные в стеке до передачи управления подпрограмме. После возврата контроля программа просто восстанавливает свои значения из стека и не обращает внимание на то, что данные в регистрах процессора могли подпрограммой перетираться.
Макроассемблер
Следующим шагом был макроассемблер. Макроассемблер — это программа для макропроцессора, который в свою очередь являлся транслятором с языка более высокого уровня (макроассемблера) в машинный код. Стало возможным создавать свои команды для, например, использования стека.
Рождаются команды работы со стеком (push, pop), команды копирования стеков данных.
Макроассемблер порождает языки более высокого уровня, за командами которых стоят десятки, а то и сотни команд процессора. FORT, ALGOL, BASIC начинают свой путь…
Модульные языки
Вкусив запретного плода расширенного синтаксиса, программисты не остановились и возжелали модульности: ведь это так удобно — вызывать отдельно написанный модуль программы и не вникать в его алгоритм. Главное — это знать как он принимает на вход данные и как возвращает результат.
Ассемблер пополняется командами, облегчающими именование и подключение модулей, передачу и возврат управления при вызове различных подпрограмм. Развиваются интерфейсы обмена данными. Возникает понятие структуры данных.
Процедурные языки
Логичным добавлением к модульнуму языку послужило понятие процедуры или подпрограммы. Подпрограмма обладает двумя важными особенностями:
1. она именованна, т.е. мы можем вызвать подпрограмму по имени
2. вызвав подпрограмму, мы точно знаем, что она вернёт управление в то же место, откуда была вызвана
К примеру, в BASIC подпрограмма вызывалась как GOSUB :Label:.
Функция
Не хватало только одного: хотелось, чтобы переменные материнской программы (из которой вызывалась подпрограмма) не портились. А то ведь как оно было? Все переменные в глобальном пространстве, начнёшь их же использовать в подпрограмме — она их и затирает.
Так было изобретено понятие функции и локальных переменных: мы вызываем именованную подпрограмму и передаём туда какие-то значения. Подпрограмма воспринимает переданные значения, как локальные именованные переменные.
С развитием функция обросла возможностью возвращать результат: до этого, ведь, как было — возвращаемое значение записывали в одну из глобальных переменных.
Функция обладает следующими особенностями:
1. она именованна
2. туда передаются параметры
3. переданные параметры доступны как именованные параметры только внутри функции, вне функции они не видны
4. функция может использовать свои локальные именованные параметры, не видные вне этой функции
5. функция может возвращать результат работы
Введение в синтаксис функции гармонично дополняет процедурные языки программирования.
Функциональные языки
Естественным желанием было дополнить функции возможностью наблюдать при вызове локальные переменные функции-родителя в вызываемой функции.
Для решения этого гении сумеречного разума порождают понятие контекста исполнения: это область именованных переменных, доступных функции во время выполнения. Эта область данных делается наследовательно-расширяемой: при при вызове дочерней функции она создаёт свой контекст, пополняемый переменными, объявляемыми внутри функции-дочки. При этом вне функции-дочки эти переменные не видны. Зато будут доступны при вызове функции-внучки, функции-правнучки и так далее.
Возможность наследовать контекст исполнения называют замыканием (closure).
Возможность полноценной работы с контекстом исполнения порождает функциональные языки программирования.
Окончательно их оформляет добавление возможности передавать функцию как параметр для вызова другой функции, а так же возвращать функцию в качестве результата выполнения подпрограммы.
Типы данных
В то же самое время мысль программистов не стояла на месте. Программисты изобретали типы данных.
Первоначально, ведь как было, данные были доступны исключительно в бинарном виде — нолики, да единички.
Людям же, для решения практических задач, удобнее оперировать абстракциями более высоких уровней. Так появляются целочисленные типы данных без возможности указать являются ли они отрицательным или нет (byte, unsigned integer, unsigned long integer и т.д.).
Потом, как развитие их — типы данных с возможностью записи отрицательного числа (кодировавшегося первым битом, в связи с чем возникали забавные казусы неравенства +0 и -0). В довершении для более удобной работы с плавающей точкой возникли типы float и double float (как нетрудно догадаться, double float — это тот же float, но с возможностью записать больше знаков как до, так и после запятой).
Интересно байтовое представление типа float — в принципе, для того, чтобы передать число нам необходим тот же integer с возможностью указать отрицательное число и или нет и указанием, через сколько разрядов от начала числа необходимо поставить точку.
Для логических операций, в принципе, вполне хватало того же нуля и единички, но для большей кузявости их обернули в тип boolean с двумя значениями true и false (за которыми, в прочем, стояли те же единичка и нолик).
Следующим типом остро потребовавшийся программистам стал массив. Массив данных кардинально отличался от стека возможностью свободного доступа не только к последнему засунутому элементу, а вообще к любому по номеру. Массив представлялся программистам как склеенные ячейки, внутри которых лежат данные, по этому изначально массив задавался сразу определённого размера и размер этот изменить было невозможно.
Но, ведь, ячейки не обязательно бывают заполненными? Так потребовалось обозначение пустой ячейки и возникает тип null. По факту, вначале он представлял из себя символ с кодом 0x0, что приводило к весёлым казусам, когда в эту самую ячейку требовалось записать нулевое значение, а потом прочитать его и интерпретировать именно как null, а не как unsigned integer со значением 0.
Для объявления массива резервировался фрагмент памяти (буфер) с указанием, сколько ячеек будет расположено в этом фрагменте, а так же какие элементы будут в нём размещаться. И не дай Ричи тебе записать в массив типа int элемент вида long! В лучшем случае повреждались последующие за ним элементы, в худшем — возникало переполнение буфера и могли повредиться другие, не относящиеся к массиву данные, расположенные сразу же за выделенным буфером памяти.
Строки, кстати, вначале появились именно как массивы символов (пришлось ввести ещё один типа данных — char, по сути соответствовавший byte). Из-за этого длину строк приходилось объявлять заранее.
Дабы справиться со строками переменной длины, придумали помечать null-маркером конец строки. Т.е., как и ранее строка представляла из себя массив, но длина этого массива задавалась сразу достаточно большой, чтобы вместить любую строку (640кб памяти хватит любым программам, ага). Строка начиналась с начала массива, а конец её помечался как null-байт, то что шло после null строкой не считалось.
Хорошая на бумаге идея помечать конец строки null-маркером при ближайшем рассмотрении оказалось ужасной: ничто не мешало добавить в середину строки null и поиметь с этого кучу лулзов. Так началась эра C-strings.
Ссылки
Организация работы с данными, как с буфером памяти породило интересную возможность при вызове функции передавать туда не сами данные, а ссылку на них.
Ранее ведь как было? В функции передавались значения переменных, значения эти копировались в именованные переменные функции дабы избежать порчи оригинальных данных.
Но ведь внутрь функции можно передать просто значение адреса выделенного фрагмента памяти и далее зачитать из него переменную любого доступного типа данных! Так появился ещё один тип данных — ссылка.
Ссылка представляет из себя ярлычок (link) на какую-то переменную, под которую выделен блочок памяти. В ЯП появляются методы работы с переменными как по значению (напрямую с этим блоком памяти), так и по ссылке (считываем из переменной указатель, потом идём по нему и уже там меняем значение в памяти).
Структуры данных
Казалось бы, за что боролись на то и напоролись: изолировали-изолировали переменные внутри функций, чтобы их не портить, а теперь даём в руки ружьё из которого ещё и в ногу можно выстрелить!
Но не тут то было: передача переменных по ссылке дало уникальную возможность конструировать из простых типов данных целые конструкции — структуры данных!
К примеру, стало возможно организовать ссылку на массив из ссылок на массивы из… Так это же целое дерево построить можно!
Естественно, сам по себе такой массив никакой практической ценности не несёт, поскольку всё это можно организовать и с помощью простых типов данных, но если добавить в программу функции типа addNode, removeNode, работающие с деревом и передавать в эти функции ссылку на структуру данных, то получается рабочая и весьма соблазнительная конструкция.
Структурные языки
Так это же получается, что программист сам может создавать свои типа данных, удобных для его программы — достаточно только создать структуру данных и описать функции работы с ними!
Так появляются структурные языки программирования. В них немедленно добавляют возможность описания нового типа данных, возможность как-то именовать этот тип и задавать для него какие-то операции.
К примеру, строку можно представить уже не просто массивом, а двусвязанным списком с функциями конкатенации через оператор + и доступом к произвольному символу через оператор [].
Начинается немедленный бурный рост структурных языков (Pascal, C), обладающих следующими особенностями:
1. в них есть формальный язык описания структур данных (*.h файлы в C)
2. в них есть возможность дать описанной структуре название (BTree)
3. в них есть возможность обозначить операции работы с этой структурой данных
Объект
Возможность создавать свои собственные типы данных возбуждает в программистах страстное желание внутри этого типа данных иметь функции для работы с ним.
Как ответ на чаяния этих светлых умов рождается концепция Объекта. Объект — это уже не просто тип данных, не просто ссылка, по которой хранится структуированная информация, но ещё и функции по обработке этой информации, доступные по этой же ссылке.
Под это подводится вселенская философия:
“Объект — некоторая сущность в виртуальном пространстве, обладающая определённым состоянием и поведением, имеющая заданные значения свойств (атрибутов) и операций над ними (методов).”
Инкапсуляция
Глубокие философские исследования позволяют осознать, что объект обладает таким свойством, как инкапсуляция, которую определяют как свойство объекта объединять в себе данные и методы работы с этими данными. Философы вообще любят рекурсивные определения.
Суть инкапсуляции же проста: объект — не объект, если состояние его (т.е. данные, которые он содержит) можно поменять не применяя методов объекта. При этом считается, что публичные переменные объекта, доступные всем и вся для изменения — это как бы тоже методы по изменению внутреннего состояния этого самого объекта.
Собственно, $object->property = 12345; считается эквивалентным методу $object->setProperty(12345);, ведь без указания в операции имени объекта $object доступа к переменной $property напрямую не получить.
Наследование
Ещё до философов Объекта программисты при работе со структурами данных очень сильно захотели и придумали, как расширять структуры данных, наследовать схему структуры-родителя в структуре-детёныше.
Создание же Объекта, объединяющего данные с функциями породило интересную инженерную задачу, как бы так извернуться: и структуру унаследовать, и функции унаследовать, да ещё бы и новые возможности в наследника добавить.
А всё дело в том, что есть у тебя в Объекте-родителе функция, есть в Объекте-наследнике функция, делают они разное, а вот чтоб имена у них — опа — одинаковые. Решение этой задачи назвали полиморфизмом.
Полиморфизм
Философы и тут подсуетились, дав определение: “Полиморфизм — возможность объектов с одинаковой спецификацией иметь различную реализацию.”. Под спецификацией тут понимается названия-сигнатуры методов работы с объектом (включая публичные переменные).
Реализаций полиморфизма множество, вот некоторые из них:
— чистый полиморфизм (полиморфизм по сигнатуре)
— параметрический полиморфизм (полиморфизм по имени метода)
— переопределение (абстрагирование, абстрактные классы)
— перегрузка (неполное замещение метода предка методом потомка)
Абстрагирование
Философская мысль тоже не стояла на месте. Изучив свойства наследования, философы поняли, что его можно заменить абстрагированием.
Абстрагирование это такая вещь… Как бы объяснить? Вот есть у вас Объект — отлично, это что-то материальное. А ещё есть представление о том, каким этот объект может быть: какие методы он должен выставлять, что эти методы должны делать, но без конкретики, так, абстрактно (напоминает заказчиков, не правда ли?). Собственно мы только что описали интерфейс объекта или абстрактного предка заветы которого можно отлить в реальность кода.
Собственно, ООП — Объектно Ориентированное Программирование. Это умение некоторых программистов работать с объектами в рамках всех трёх концепций: инкапсуляции, наследования и полиморфизма. Ну или инкапсуляции, абстрагирования и полиморфизма.
Никакого отношения к модели MVC парадигма OOP не имеет (в отличие от мнения некоторых PHP программистов). ООП — это просто работа с данными и методами обработки данных, как с наследуемыми объектами.
В отличие от парадигмы процедурного и структурного программирования, где если и есть объекты, то они не наследуемы. Ну или и объектов-то нет, все данные передаются в массивах, структурах, выделенных буферах памяти.
Класс-ориентированное программирование
Объектное программирование требует создания множества объектов (как ни странно). Соответственно требуются как-то организовать иерархию объектов, как-то скучковать их.
В ответ на эти чаяния была разработана концепция класс-инстанс. Что такое класс? Класс — это набор методов и функций без данных. Сам по себе класс — это нечто нерабочее, для работы нужны данные. Собственно, для того чтобы получить рабочий объект необходимо инстанциировать класс — сказать “создай мне объект с теми функциями, что описанные в этом классе и данными, которые я тебе сейчас скажу”.
Фактически класс — это такой синтаксический кусище сахара, который позволяет не просто описать API объекта (как это делает интерфейс), но и задать функции по обработке данных.
Система классов позволяет формально описать свойства объекта, правила наследования свойств объектов, правила доступа к данным объекта. Использование классов задаёт парадигму класс-ориентированного программирования.
Класс — прикольная штука, но не необходимая для ООП, поскольку бывают объектно-ориентированные языки прекрасно обходящиеся и без классов.
Прототипное программирование
Другим способом задавать наследование является прототип. При прототипном программировании нет инстансов объектов, объект существует в уникальном виде. Но для каждого объекта можно задать прототип или прототипы — список объектов, свойства и методы которых он будет наследовать.
Исторически модель наследования через прототипы, которую разделяют такой язык, как JavaScript, является более старой, чем через описание классов. Но класс-ориентированное программирование оказалось более удобным для описания API и фреймворков (а как известно, каждый половозрелый Java программист обязан написать свой фреймворк, так же как половозрелость PHP программиста определяется по самописной CMS), по этому стало более распространённым.