Самая Большая Проблема Информатики

.

это, конечно, именование сущностей.

И я говорю не только об именах переменных или новых технологиях, нет. Мы не можем договориться даже по самым элементарным вопросам.



Тысяча диалектов

Вы это знаете Спецификация Часто ли в языке программирования C упоминается термин «объект»? Нет, это не объект, как его описывают в ООП — объект в C определяется как «блок данных во время выполнения, содержимое которого может представлять некоторую ценность».

В таком понимании объекта имеет смысл говорить, например, об «объекте типа char».

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

Таким образом, язык программирования Java либо имеет функции, либо не имеет их, в зависимости от того, кого вы спрашиваете.

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

Даже в рамках одного языка программирования мы иногда путаемся.

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

Я часто использую термин «интерфейс функции» (« подпись "), но другие люди делают это очень редко, поэтому иногда мне интересно, понимает ли кто-нибудь вообще, о чем я говорю? Когда мы говорим «тип данных с плавающей запятой», программист C услышит «тип данных с плавающей запятой одинарной точности», но программист Python будет уверен, что мы имеем в виду тип двойной точности.

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

Частично проблема в том, что когда мы говорим о «информатике», мы на самом деле не говорим о информатике.

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

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

Человек, который начинает изучать программирование с помощью Javascript, будет иметь определенное представление о том, что такое «класс», и оно будет сильно отличаться от представления человека, чьим первым языком был Ruby. Люди переходят из одного языкового фона в другой и начинают обвинять дело, например, в том, что не бывает нормальных замыканий, поскольку на их языке термин «замыкание» означал совсем другое.

Иногда со всем этим можно как-то смириться.

И иногда может случиться конфуз.

Вот мои (наименее?) любимые примеры таких ситуаций.



Массивы, векторы и списки

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

int[5] описывает массив, предназначенный для хранения пяти переменных типа int, подряд одной за другой.

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

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

Но ждать! В C++11 появился термин «initializer_list», в названии которого есть слово «list», но по сути это массив.

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

Haskell работает по тому же принципу, плюс у него есть Data.Array для быстрого доступа к элементам по индексу.

В Perl типом последовательности является массив, хотя само слово «тип» здесь не очень уместно, это скорее форма переменной.

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

Это довольно странная вещь, и объяснение заняло бы больше абзаца, поэтому я даже не буду здесь начинать.

В Python список — это фундаментальный тип данных, который имеет свойства, аналогичные вектору в C++ и (в CPython) реализован как массив C. Стандартная библиотека также предоставляет редко используемый тип данных массива, который упаковывает числа в массивы C для экономии места и сбивает с толку программистов, пришедших на Python через C, заставляя их думать, что «массив» — это просто то, что используется по умолчанию.

Ах да, существует также встроенный тип массива байтов, который отличается от массива, в котором хранятся байты.

Javascript имеет тип массива, но он построен на основе хеш-таблицы со строковыми (!) ключами.

Существует также ArrayBuffer для хранения чисел в массивах C (очень похоже на тип массива в Python).

В PHP тип данных, называемый массивом, на самом деле представляет собой упорядоченную хеш-таблицу со строковыми (!) ключами.

В PHP тоже есть списки, но это не тип данных, а просто некий синтаксический сахар.

Люди, переходящие с PHP на другие языки, иногда удивляются, что классические хеш-таблицы не сохраняют порядок.

Язык Lua отвергает все традиции, вообще не используя термины массив, вектор или список.

Единственный доступный тип данных называется таблицей.

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

В C++11 добавлен unordered_map, который представляет собой хеш-таблицу) JavaScript :объект (!) (это на самом деле не классический ассоциативный массив, но он может хранить значения, доступные по строковому ключу.

Также существует тип данных Map.) Луа : стол PHP : массив (!) (и только строковые ключи) Перл :hash (тоже «форма», а не тип, плюс неоднозначность из-за того, что хеши тоже называются совсем по-другому, плюс опять же только строковые ключи) Питон :дикт Ржавчина :map (хотя существует два отдельных типа — BTreeMap и HashMap)

Указатели, ссылки и псевдонимы

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

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

Указатель — это просто индекс в этом большом блоке данных.

C++, унаследовав указатели от C, сразу предостерегает вас от злоупотребления ими.

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

Это сразу же создает новую (очень странную) особенность, которой не было в C: две локальные переменные могут указывать на один и тот же блок данных в памяти, поэтому строка a=5; вполне может изменить значение переменной b. В Rust есть ссылки, и они даже используют синтаксис C++, но по сути являются «заимствованными указателями» (то есть указателями, но прозрачными).

В языке также есть менее распространенные «чистые указатели», использующие синтаксис указателей C. В Perl есть ссылки.

Даже два отдельных типа ссылок.

Жесткие ссылки (аналогичные указателям в C, за исключением того, что адрес недоступен и не предназначен для прямого использования) и мягкие ссылки, где вы используете содержимое переменной в качестве имени другой переменной.

В Perl также есть псевдонимы, которые работают аналогично ссылкам в C++, но не работают с локальными переменными и по сути вообще не являются типом данных, а просто манипулируют таблицей символов.

В PHP есть ссылки, но, несмотря на влияние Perl, синтаксис ссылок был взят из C++.

C++ идентифицирует ссылку по типу переменной, на которую она ссылается.

Но в PHP нет объявления переменных, поэтому переменная начинает считаться ссылкой в тот момент, когда она участвует в определенном наборе операций, включающих оператор &.

Этот магический символ «заражает» переменную «референциальностью».

Python, Ruby, JavaScript, Lua, Java и множество других языков не имеют указателей, ссылок или псевдонимов.

Это несколько затрудняет понимание этих языков людьми, выходцами из мира C и C++, поскольку в ходе объяснения некоторых высокоуровневых вещей часто приходится произносить фразы типа «это указывает на…», «это относится к to.», что вводит людей в заблуждение, заставляя их думать, что на самом деле у них есть некий указатель или ссылка на некоторую область памяти, к содержимому которой можно получить прямой доступ.

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



Передача по ссылке и по значению

Кстати, о ссылках.

я уже объяснил Ранее это было для Python, но я снова напишу сокращенную версию.

Вся эта дихотомия не имеет смысла в большинстве языков, потому что ключевой вопрос здесь заключается в том, что C считает значением, и этот вопрос не имеет никакого смысла за пределами семейства языков, родственных C. Фундаментальная проблема здесь в том, что в C есть синтаксис описания структур, но семантика самого языка не видит структуры в коде — только набор байтов.

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

" оператор.

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

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

А, ну да, а еще можно посмотреть какую-то отдельную часть данных.

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

При присвоении одной структуры другой происходит банальное побайтовое копирование, такое же, как и при присвоении, например, переменных типа double. Грань иллюзорна.

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

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

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

В C++ введена концепция ссылки, на тот случай, если вдруг все в C с его указателями окажется для вас слишком простым и понятным.

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

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

Эту «передачу по ссылке» лучше называть «передачей по псевдониму».

Java, Python, Ruby, Lua, JavaScript и многие другие языки используют контейнеры как отдельные сущности.

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

нет, не ссылаются, они указывают. (нет, не ссылаются).

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

Если я скажу «ссылки на обе переменные», вы можете подумать, что речь идет о ссылках C++ (псевдонимах).

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

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

Семантически языки ведут себя так, как если бы содержимое переменных (их значения) существовало само по себе, в каком-то абстрактном мире, а переменные — это просто имена.

Присвоение связывает имя со значением.

Соблазнительно объяснить это новичкам как «теперь a указывает на b» или «теперь они ссылаются на один и тот же объект», но эти объяснения добавляют косвенность, которой на самом деле не существует в языке.

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

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

Внутри вызываемой функции нельзя переназначать переменные: переменная в этом случае не является псевдонимом; присвоение ему значения только свяжет его имя (внутри функции) с новым значением, но никак не повлияет на переменные в вызывающем коде.

Все это несколько выходит за рамки классической «передачи по ссылке» и «передачи по значению».

Устоявшейся терминологии вообще не существует; Я слышал, что это называется переносом объектов, переносом по имени, переносом путем деления.

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

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

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

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



Бесплатный набор текста

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

Я помню:

  • Есть сильный типизация, что означает, что переменная не меняет свой тип, чтобы «адаптироваться» к операциям, которые код хочет с ней проделать.

    Rust — строго типизированный язык, сравнение 32-битных и 64-битных целочисленных значений выдаст ошибку.

  • Есть слабый типизация, что означает, что переменная может изменить свой тип, чтобы соответствовать вычисляемому выражению.

    JavaScript — слабо типизированный язык, в котором 5 + «3» неявно преобразует строку в число и выражение вернет результат 8 (шучу, результат будет, конечно же, «53»).

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

  • Есть статический типизация, что означает, что тип переменной известен на этапе компиляции.

    Java — статически типизированный язык.

    Достаточно взглянуть на любой Java-код — кажется, что он на 70% состоит только из названий используемых типов.

  • Есть динамичный типизация, что означает, что тип переменной определяется во время работы программы.

    Ruby — динамически типизированный язык, типы определяются во время выполнения.

Понятия «сильная» и «слабая» типизация создают гармоничную картину мира.

«Статическая» и «динамическая» типизация также понятны и дополняют друг друга.

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

Например, хотя язык Go считается статически типизированным, интерфейс {} в нем имеет особенности динамической типизации.

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

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

Haskell — сильный и статический, C — слабый и статический, Python — сильный и динамичный, Shell — слабый и динамичный.

Что же такое «свободная типизация»? Некоторые говорят, что это похоже на «слабый», но многие люди называют Python «слабо типизированным», хотя Python — строго типизированный язык.

(По крайней мере, сильнее, чем C!).

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

Тут надо отметить, чья бы корова мычала, а C++ промолчал бы.

Система типов C++ не лишена недостатков.

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

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

Кэширование

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

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

Классическая оптимизация, а точнее обмен памяти на скорость.

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

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

Это очень сбивает с толку.

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

   

class Monster: def think(self): # do something smart @reify def inventory(self): return []

Здесь monster.inventory на самом деле не существует, пока вы не попытаетесь его прочитать.

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

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

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

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

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

нужный.

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

Когда я говорил, что видел reify во многих проектах, я имел в виду «многие проекты скопировали и вставили или написали реализацию reify на коленях».

И наконец, этот компонент был добавлен в репозиторий под названием.

кэшированное свойство .

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

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

Пример выше несколько простой, но даже для него «инвалидация» кеша приведет к необратимым последствиям — мы полностью потеряем состояние Monster.inventory. Реальные приложения @reify часто открывают файлы или соединения с базами данных, и в этих случаях «инвалидация» будет означать уничтожение данных.

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

Да, вы также можете создать кеш, используя @reify. Вы также можете создать его, используя dict и другие методы.

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

Таким образом, присвоение имен сущностям — действительно самая важная проблема в информатике.

Теги: #нейминг #программирование #Анализ и проектирование систем #Идеальный код #C++

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.