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

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


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

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

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

Программистов на Python можно поймать на употреблении термина «свойство» (property) вместо аттрибут (attribute), хотя оба термина существуют в языке и они не абсолютно тождественны. Есть разница между «аргументом» и «параметром», но кому до этого есть дело — мы просто произносим то или иное слово, когда нам кажется удобнее. Я часто использую термин «интерфейс функции» ("signature"), но другие люди делают это очень редко, так что иногда я задумываюсь — понимает ли вообще кто-нибудь, о чём я говорю?

Когда мы говорим «тип данных float», то программист на С услышит «тип с плавающей запятой одинарной точности», а программист на Python будет уверен, что имелся в виду тип с двойной точностью. И это ещё не самый страшный случай, поскольку когда упоминается тип word — это вообще может подразумевать как минимум четыре различных толкования в плане его размера.

Часть проблемы в том, что когда мы говорим «о компьютерных науках» мы на самом деле не говорим о компьютерных науках. Мы занимаемся практическим программированием на каком-то множестве (из сотен!) неидеальных языков программирования, каждый из которых — со своими особенностями и причудами. При этом у нас есть некоторое(ограниченное) количество знакомых нам терминов, которые мы применяем к разным фичам разных языков, иногда к месту, а иногда и не очень. Человек, начавший изучать программирование с Javascript, будет иметь определённое представление о том, что такое «класс» и оно будет очень отличаться от представления того человека, чьим первым языком был Ruby. Люди приходят с бэкграундом одного языка в другой и начинают обвинять его, например, в том, что там нет нормальных замыканий, поскольку в их языке термином «замыкание» обозначалось нечто совсем другое.

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

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


В языке С массив — это последовательный блок данных, в который вы можете поместить некоторое (чётко определённое) количество значение переменных одного типа. int[5] описывает массив, предназначенный для хранения пяти переменных типа int, непосредственно одна за другой.

С++ вводит понятие вектора, как аналога массива, способного автоматически изменять свой размер, подстраиваясь под текущие потребности. Также есть стандартный тип списка, под которым в данном случае понимается двусвязный список (на самом деле стандарт не выдвигает требований к конкретной реализации, но требования по функционалу делают логичным реализацию вектора на базе массива, а списка — на базе двусвязного списка). Но постойте! В С++11 вводится термин «initializer_list», в названии которого есть слово «список» (list), но по сути он является массивом.

Списки в Lisp являются, конечно же, связными списками, что делает простым их обработку в плане доступа к голове и хвосту. Так же работает и Haskell, плюс в нём есть Data.Array для быстрого доступа к элементам по индексу.

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

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

В Javascript есть тип массива, но он построен поверх хэш-таблицы со строковыми (!) ключами. Есть также ArrayBuffer для сохранения чисел в С-массивах (очень похоже на тип array в Python).

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

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

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

C++: map (а на самом деле это двоичное дерево. С++11 добавляет unordered_map, который является хэш-таблицей)
JavaScript: object (!) (это вообще-то не классический ассоциативный массив, но в нём можно хранить значения, доступные по строковому ключу. А ещё есть тип данных Map.)
Lua: table
PHP: array (!) (и только строковые ключи)
Perl: hash (тоже «форма», а не тип, плюс неоднозначность из-за того, что хэшами называют также нечто совершенно другое, плюс опять-таки только строковые ключи)
Python: dict
Rust: map (хотя существует в виде двух отдельных типов — BTreeMap and HashMap)

Указатели, ссылки и алиасы


В языке С есть указатели, которые являются адресами хранения некоторых данных в памяти. Для С это естественно, поскольку всё в С — об управлении данными в памяти и о представлении всех данных, как адресов в одном большом блоке данных (ну, более или менее так). Указатель — всего лишь индекс в этом большом блоке данных.

С++, унаследовав указатели из С, сразу предостерегает вас от злоупотребления ими. В качестве альтернативы предлагается ссылка, которые вроде бы в точности как указатели, но для доступа к значениям в которых не нужно использовать оператор "*". Это сразу создаёт новую (очень странную) возможность, которой не было в С: две локальных переменных могут указывать на один и тот же блок данных в памяти, так что строка а=5; вполне себе может изменить значение переменной b.

В Rust есть ссылки, и они даже используют синтаксис С++, но по факту являются «заимствованными указателями» (т.е. указателями, но прозрачными). Также в языке есть менее распространённые «чистые указатели», которые используют синтаксис указателей С.

В Perl есть ссылки. Даже два отдельных типа ссылок. Жесткие ссылки (аналог указателей в С, за тем лишь исключением, что адресс недоступен и подразумевается, что он не должен быть использован непосредственно) и мягкие ссылки, где вы используете содержимое некоторой переменной в качестве имени другой переменной. Также в Perl есть алиасы, которые работают аналогично ссылкам в С++ — но не работают для локальных переменных и вообще по сути не являются типом данных, а просто манипуляцией над символьной таблицей.

В PHP есть ссылки, но не смотря на влияние Perl, синтаксис ссылок был взят из С++. С++ определяет ссылку по типу переменной, на которую она ссылается. Но в PHP нет объявления переменных, так что переменная начинает считаться ссылкой с того момента, как она участвует в некотором специфическом наборе операций, включающем оператор &. Этот магический символ «заражает» переменную «ссылочностью».

Python, Ruby, JavaScript, Lua, Java и ещё куча языков не имеют указателей, ссылок или алиасов. Это несколько затрудняет понимание этих языков для людей, пришедших из мира С и С++, поскольку по ходу объяснения тех или иных высокоуровневых вещей часто приходится говорить фразы вроде «это указывает на...», «это ссылается на ...», что вводит людей в заблуждение, создавая впечатление, что у них действительно есть некоторый указатель или ссылка на некоторую область в памяти, к содержимому которой можно непосредственно получить доступ. По этой причине я называю поведение ссылок в С++ алиасингом, поскольку это более ясно отражает суть происходящего и оставляет слово «ссылаться» для более общего применения.

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


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

Фундаментальная проблема здесь в том, что С имеет синтаксис для описания структур, но сама семантика языка структур в коде не видит — лишь набор байтов. Структура вроде бы выглядит как контейнер, хороший такой надёжный контейнер: содержимое заключено в фигурные скобки, нужно использовать оператор "." для доступа к внутренним членам. Но для С ваша структура — всего лишь блок бинарных данных, не сильно отличающийся от int, ну разве что немного больший по размеру. Ах, ну да, и ещё можно посмотреть на какую-то отдельную часть данных. Если вы помещаете одну структуру внутрь другой, язык С тупо выделит во внешней структуре блок данных для внутренней. Когда вы присваиваете одну структуру другой — происходит банальное побайтовое копирование, такое же, как при присваивании, например, переменных типа double. Грань иллюзорна. В результате, единственный действительно «настоящий» контейнер в языке С — это указатель!

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

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

Java, Python, Ruby, Lua, JavaScript и многие другие языки оперируют контейнерами как отдельными сущностями. Если у вас есть переменная, внутри которой есть структура и вы присваиваете эту переменную другой переменной, то по факту никакого копирования не происходит. Просто теперь обе переменные ссылаются… нет, не ссылаются, указывают...(нет, не указывают)…

И вот она — проблема терминологии! Когда кто-нибудь спросит, передаёт ли язык Х параметры по значению или по ссылке — скорее всего этот человек мыслит в терминах модели языка С и представляет все остальные языки как нечто, что должно обязательно так или иначе ложиться на эту фундаментальную модель. Если я скажу «обе переменные ссылаются», то можно подумать, что речь идёт о С++ ссылках (алиасинге). Если я скажу «обе переменные указывают», то можно решить, что речь идёт об указателях в стиле С. Во многих случаях в языке может не быть ни первого, ни второго. Но в английском языке нет других слов для выражения того, о чём мы хотим сказать.

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

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

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

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

Свободная типизация


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

  • Есть сильная типизация, которая означает, что переменная не меняет свой тип чтобы «подстроиться» под те операции, которые код хочет с ней сделать. Rust — язык с сильной типизацией, сравнение 32-битного и 64-битного целых значений вызовет ошибку.
  • Есть слабая типизация, которая означает, что переменная может изменить свой тип ради того, чтобы подойти к вычисляемому выражению. JavaScript — слабо типизированный язык, в нём 5 + «3» неявно сконвертирует строку в число и выражение даст результат 8 (шучу-шучу, результат будет, конечно же, «53»). Также слабо типизированным является С: вы можете просто взять и присвоить переменной типа int значение «3» и получить хоть и чудаковатый, но вполне компилирующийся код.
  • Есть статическая типизация, которая означает, что тип переменной известен на этапе компияции. Java — язык со статической типизацией. Вы только посмотрите на любой Java-код — такое впечатление, что он на 70% состоит из одних только названий используемых типов.
  • Есть динамическая типизация, которая означает, что тип переменной определяется по ходу выполнения программы. Ruby — язык с динамической типизацией, типы определяются на этапе выполнения.

Понятия «сильной» и «слабой» типизации создают гармоничную картину мира. «Статическая» и «динамическая» типизации тоже понятны и взаимодополняемы. Языки могут иметь в себе элементы и сильной и слабой типизации, так же как статической и динамической, хотя какая-то одна позиция всё-же является превалирующей. Например, хотя язык Go считается статически-типизируемым, interface{} в нём имеет признаки динамической типизации. И наоборот, Python формально статически-типизированный и каждая переменная имеет тип object, но удачи вам с этим.

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

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

И, поскольку термин «свободно типизируемый» я в основном встречаю в уничижительном смысле, могу предположить, что люди имеют в виду «не так типизируемый, как это происходит в С++». Тут, надо отметить, что чья бы корова мычала, а С++ помалкивал бы. Система типов С++ далеко не без изъянов. Какой, например, будет тип у указателя на тип T? Нет, это не T*, поскольку ему можно присвоить нулевой указатель (а это совсем не указатель на переменную типа T) или случайный мусор (что тоже вряд ли будет указателем на переменную типа Т). Какой смысл гордиться статической типизацией, если переменные некоторого типа по факту могут не содержать в себе значение данного типа?

Кэширование


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

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

class Monster:
    def think(self):
        # do something smart

    @reify
    def inventory(self):
        return []

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

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

reify длительное время не был представлен в репозиториии PyPI в качестве отдельного компонента. Наверное, потому, что его можно реализовать с нуля в десяток строк. Когда я говорил о том, что видел reify во многих проектах, я имел в виду «многие проекты скопипастили или написали на коленке реализацию reify». И вот, наконец, данный компонент был добавлен в репозиторий под именем… cached-property. Документация даже показывала как можно «инвалидировать кэш» — порчей внутреннего состояния объекта.

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

Да, с помощью @reify можно создать и кэш. А ешё его можно создать с помощью dict и разными другими способами тоже.

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

Комментарии (49)


  1. A-Stahl
    28.12.2016 12:21
    +6

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

    Но я готов помочь капитану Очевидность. Я готов прямо сейчас решить вашу " Сложнейшую проблему". Достаточно определять контекст.
    Говорите: «Язык Си». Всё, контекст задан. И никто уже не попытается изменять размер массива. Всё просто.


    1. tangro
      28.12.2016 13:20

      Говорите: «Язык Си». Всё, контекст задан.

      Ок. «Язык С». А ну-ка поведайте нам, например, размер типа int в «языке С»?


      1. A-Stahl
        28.12.2016 13:22

        В чём проблема? Не меньше двух байт вроде. Ну может уже 4 минимум. За формальными стандартами особо не слежу.


        1. tangro
          28.12.2016 15:07
          +3

          В том, что может быть 2, 4, 8 или вообще сколько угодно. Контекста «язык программирования» не достаточно. Нужен ещё как минимум компилятор и ОС. А иногда и другие параметры.


          1. Exbe
            28.12.2016 15:33

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


          1. A-Stahl
            28.12.2016 15:56
            +4

            >Контекста «язык программирования» не достаточно.
            Это значит, что для типа int более точная информация не важна. В контексте Си. 40 лет такой размытый int всех устраивал.
            В Си++, например, int тоже «размытый», но если нужна конкретика, то есть типы, например, int64_t.
            Возможно в новых стандартах чистого Си тоже что-то такое есть.

            А так ваша претензия звучит как: «Слово бочка — идиотское. Не указан объем. Как можно пользоваться словом бочка и при этом не указывать ни объём ни материал?»


            1. tangro
              28.12.2016 18:39

              Я нигде не говорил, что тип int — идиотский и им нельзя пользоваться. Я говорил, что «язык» != «полный контекст». А какой контекст является полным — у каждого тоже своё мнение.


      1. LLWE_FUL
        28.12.2016 18:39

        > размер типа int в «языке С»?

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

        2) sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)

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


        1. A-Stahl
          28.12.2016 19:27
          -4

          >char всегда 1 байт
          Это не так. Точнее это так de facto в данный момент, но завтра всё может измениться.
          Char по определению это тип, достаточный для хранения символа в «базовой» кодировке (Smallest addressable unit of the machine that can contain basic character set). Если завтра однобайтные кодировки вымрут и останется лишь один Юникод, то char станет 2 и более байт.
          Если бы char был привязан к байту, то и назывался бы он byte или как-то так:)


          1. LLWE_FUL
            28.12.2016 19:37

            Ну серьезно. Сишной документации под рукой нет, но есть спек с++98, который вряд ли сильно отличается от с98 в этой части. Цитирую:

            1.7 The C++ memory model
            The fundamental storage unit in the C++ memory model is the byte. A byte is at least large enough to contain any member of the basic execution character set and is composed of a contiguous sequence of bits, the number of which is implementation-defined.


            3.9.1 Fundamental types
            Objects declared as characters (char) shall be large enough to store any member of the implementation’s basic character set


            5.3.3 Sizeof
            The sizeof operator yields the number of bytes in the object representation of its operand. [...] sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1;


            Параграф 1.7 дает определение байта в контексте языка С++, 3.9.1 предъявляет к char такие же требования по размеру, которые предъявлены к байту в параграфе 1.7, параграф 5.3.3 явно говорит, что размер char равен одному байту.

            Судя по всему, вы думали о байте в контексте кодировок, а там в этот термин вкладывается немного другой смысл :)


            1. A-Stahl
              28.12.2016 19:45
              -1

              Ок, может и так. А что тогда значит «member of the basic execution character set».
              Точнее что такое «basic execution character set»?
              Судя по первой ссылке из гугла https://msdn.microsoft.com/en-us/library/09k5ez9h.aspx это всё-таки имеет прямое отношение к обычным текстовым кодировкам.
              Так что я не вижу ошибки в своём мнении.
              Ваш ход:)


              1. LLWE_FUL
                28.12.2016 20:02

                2.2 Character sets
                The basic source character set consists of 96 characters: the space character, the control characters representing horizontal tab, vertical tab, form feed, and new-line, plus the following 91 graphical characters [...]
                [...]
                The basic execution character set [...] shall [...] contain all the members of the basic source character set, plus control characters representing alert, backspace, and carriage return, plus a null character.


                Про кодировки ни слова. Языку не интересно, как эти символы кодируются битами и машинными байтами, но байт в языке С определен как «at least large enough to contain any member of the basic execution character set». Если завтра авторы gcc решат заменить внутреннее представление символов с ASCII на UCS-2, — это их право. Тогда каждый символ будет занимать два машинных байта, но все еще один сишный байт.

                > Так что я не вижу ошибки в своём мнении.
                Ваше мнение прямо противоречит параграфу 5.3.3 стандарта.

                Обратите внимание, что вы упали именно в ту яму, которая описана в посте: термин байт имеет множество смыслов в зависимости от контекста, и мы сейчас спорим о том, чем он является, а чем — нет, но используем разные контексты. Так что спорить мы можем бесконечно, пока не осознаем бессмысленность этого занятия (в контексте языка С прав я, в контексте UTF-8 правы вы: в RFC термины byte и octet используются как взаимозаменяемые, и octet определен как 8 бит).


                1. A-Stahl
                  28.12.2016 20:07

                  >прямо противоречит параграфу 5.3.3 стандарта.
                  Тут мне нечего возразить.
                  Хотя тема довольно мутная всё равно. Во всяком случае для меня.


                  1. LLWE_FUL
                    28.12.2016 20:11
                    +2

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


        1. lolikandr
          29.12.2016 20:27
          +1

          Это хорошо, когда программист читает документацию на стандарт.
          Но главное — читать документацию на конкретный компилятор!
          Мы просто обалдели, когда компилятор выдал sizeof(char) = 1 и sizeof(double) = 2.
          И ещё больше удивились, когда почитали

          документацию на компилятор
          Документация на компилятор: http://noel.feld.cvut.cz/vyu/scs/ti/TMS320C2X-2XX-5X-C-Compiler.pdf
          image


          1. LLWE_FUL
            29.12.2016 21:29

            > Мы просто обалдели
            > И ещё больше удивились
            Для этого и нужно читать стандарт языка, чтобы не полагаться на имперические знания, почерпнутые из общения с одной платформой, при переходе на другую платформу. Там еще много таких интересных мест в стандарте есть, которые на вашем компайлере могут работать совсем не так, как вы привыкли на vc++/g++ ;)


    1. beroal
      30.12.2016 13:40
      +1

      Тогда в каждом языке программирования будет своя наука. Я против этого! Информатика (computer science) едина для всех программистов. Должна быть едина.


  1. Tiendil
    28.12.2016 12:38

    Хорошее описание проблемы.

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

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


    1. A-Stahl
      28.12.2016 12:41
      +4

      Тут совсем не так просто. Ведь почему существует так много разных языков? Да потому что они действительно разные. И между Фортом и Явой принципиальная пропасть.
      Создание терминологии, которая однозначно сможет описать все нюансы всех языков — дело неблагодарное. Эта терминология получится настолько масштабной, что ей банально никто не будет пользоваться.


      1. Tiendil
        28.12.2016 12:45

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

        Например, в данный момент довольно легко сделать стандарт на именование машинных типов данных (например, int8, int16, etc...), но просто так массово на него переходить никто не будет, потому что крайне дорого.


        1. A-Stahl
          28.12.2016 13:00
          +2

          >довольно легко сделать стандарт на именование машинных типов данных (например, int8, int16, etc...)
          Только довольно-таки бесполезно.
          Завтра выпустят машину с 9-битным байтом. Вводить в стандарт новые типы? Ок, пропишем в стандарте наименование целочисленных типов как int<количество бит>. Автоматически станут валидными типы вроде int9.
          Потом кто-то начнёт выпускать машину с троичным битом и выражение int8 станет совершенно странной и нелепой хренью, которая никому ничего не объясняет. Указывать ещё и разрядность бита? Ок, получит универсальный и достаточный для понимания тип int_3_8, способный хранить 43М значений. Но кто в здравом уме будет использовать этот треш: int_3_8?
          Не, это утопия. Бессмысленная и беспощадная.


    1. Jamdaze
      28.12.2016 14:25

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


  1. mayorovp
    28.12.2016 13:05
    +4

    О переводе.


    1. Вообще-то, говоря о функциях, "signature" переводится не как "интерфейс", а калькой — "сигнатура".


    2. "Передача дележом" — это "pass by sharing"? Устоявшегося перевода не назову, но такой перевод читаю первый раз.

    Теперь о содержании.


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


    2. В PHP &-операторы не "заражают" никакие переменные ссылочностью. Ссылочность относится к значениям переменных!


    1. tangro
      28.12.2016 13:17
      +1

      Вообще-то, говоря о функциях, «signature» переводится не как «интерфейс», а калькой — «сигнатура».

      Там в комментариях к оригинальной статье автор поясняет что он имел в виду под словом «signature», так вот он говорит «A function “signature” is just its interface: the arguments it takes, their names, their types, the return type, and exceptions that may be thrown.». А вообще, Ваш комментарий отличный пример той самой описанной в статье проблемы с именованием сущностей.

      «Передача дележом» — это «pass by sharing»? Устоявшегося перевода не назову, но такой перевод читаю первый раз.

      Да, это «pass by sharing». А если устоявшегося термина нет, то нужно же как-то первый раз это назвать. Можно ещё предложить «разделением доступа» или «одалживанием», но это не лучше и не хуже.


    1. alexkunin
      28.12.2016 13:27

      Таки заражают. Недавно на хабре пробегала статейка — не могу найти, но смысл сводится к вот этой более старой, на английском: PHP: References To Array Elements Are Risky, первый же пример демонстрирует проблему.

      А в комментариях этой вакханалии дано техническое объяснение, я его тут целиком приведу:

      PHP has two kinds of variables, and a variable can switch between the two:

      — non-reference variables. New variables are created like this. Assigning them to another variable by value increases an internal reference count, it doesn't actually copy the value. Write access copies the variable if the refcount is >1, otherwise it modifies the variable. Assigning the variable to another variable by reference is treated as write access; this makes sure that the implicit reference in by-value assignment doesn't become explicit, but it also is the kind of «assignment affects the source» behavior you described. Yes, it affects the source — it is a write access that triggers copy-on-write AND it turns the source variable into a reference variable.

      — reference variables. There are at least two names for this variable, and they are explicit references, i.e. no copy-on-write. If the refcount drops to 1, the variable becomes a non-reference variable again. The latter effect makes unset() cancel the effect in your example.
      Ну, кроме как в массивах я особых побочных эффектов не встречал, так что это «заражение» опасно только для элементов массива (вроде бы).

      И еще вот оттуда же: кишки и расчлененка на тему ссылок в виде статьи об интерналсах.


  1. WinPooh73
    28.12.2016 13:19

    > C++: map (а на самом деле это двоичное дерево. С++11 добавляет unordered_map, который является хэш-таблицей)

    Вроде бы, стандарт C++ не обязывает реализовывать map именно двоичным деревом, а unordered_map — хэш-таблицей.


    1. tangro
      28.12.2016 15:10

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


      1. WinPooh73
        28.12.2016 15:16

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


        1. mayorovp
          28.12.2016 16:11
          +2

          На хеш-таблице вы не сможете реализовать операции lower_bound и upper_bound за адекватное время.


    1. RdSpclL
      28.12.2016 18:41

      map точно не обязывает, а еще его часто реализовывают красно-черным деревом


      1. mayorovp
        29.12.2016 10:11
        +3

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


        1. RdSpclL
          03.01.2017 15:25

          я как раз и конкретизировал


  1. tsklab
    28.12.2016 14:24
    +2

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


    1. allter
      28.12.2016 14:50

      Само определение в статье, которую вы предложили, уже содержит как минимум одно «и/или» (размер регистров и/или размер шины данных). И потом, можно перебирать разные регистры. Итак, на самой популярной десктопной архитектуре, каков размер слова? 8? 16? 32? 64? 80? 128? 256? Или 512 бит?

      Ну и, например, в контекте языка unix shell, word — совсем другое. :)


  1. allter
    28.12.2016 14:29

    Вспомнился комикс про философский смысл названий (сорри, нету ссылки на страничку):
    http://static.existentialcomics.com/comics/philosophyFriends1.png
    http://static.existentialcomics.com/comics/philosophyFriends2.png


  1. aso
    28.12.2016 14:40

    С каких это пор Питон стал языком со строгой типизацией? (И, кстати — что это такое?)
    «Список инициализаторов» (как и список параметров функции") — это синтаксические конструкции языка, возможно вообще не имеющих однозначного представления в памяти.
    Вообще, в тексте куча мелких шероховатостей и неясностей — непонятно откуда взявшихся, то ли из оригинала, то ли от переводчика.


    1. mayorovp
      28.12.2016 14:57
      +3

      С каких это пор Питон стал языком со строгой типизацией?

      С рождения.


      > 3+"4"
      < TypeError: unsupported operand type(s) for +: 'int' and 'str'

      Невозможность сложить число и строку — отличительный признак языка со строгой типизацией.


      1. mayorovp
        28.12.2016 15:02

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


        1. tangro
          28.12.2016 15:12
          +1

          Кстати, да, я вроде бы пытался везде использовать термин «сильная типизация», но вот в одном месте пропустил. Сейчас исправлю.



  1. sbnur
    28.12.2016 15:11

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


  1. darkslave
    28.12.2016 15:12

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


  1. Vlad_fox
    28.12.2016 18:10

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

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

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

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


  1. VolCh
    28.12.2016 18:30

    лично я уверен, что такой штуки как «свободная типизация» не существует


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


    1. LLWE_FUL
      28.12.2016 20:23
      +1

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


      1. VolCh
        29.12.2016 10:39

        Не пришла в голову такая формулировка, поэтому написал «как бы приведение». Сути это не меняет — у данных в памяти нет определенного типа.


  1. samsergey
    29.12.2016 12:29
    +2

    Спасибо, это очень хорошая статья.
    Она могла бы быть отличным введением к разделу «Семантика языков программирования» в курсе «Теория ЯП». Теперь, по её прочтении, можно смело рассказывать студентам о том, зачем нужны такие «скучные» вещи, как операционная, аксиоматическая и денотационная семантика; что такое контракты, для чего появился язык Racket и чем силён Eiffel; почему в Haskell и Scala используют такие странные слова, как «моноид», «функтор» или «комонада» (что это не «хипстерская мода» и не умничание, а денотационная семантика соответствующих конструкций, которые можно обнаружить и в других языках); зачем, наконец, имея в своём распоряжении С, С++ и Java, сочинять и развивать новые языки программирования, подчас, странные, «академические» (с плохо проработанной операционной семантикой) или вовсе «фриковские».
    Излагаемая в статье проблема — это проблема программирования, а не Computer Science. Параллельно с изобретением новых конструкций и концепций программирования (которое и порождает разнообразие и несогласованность терминов) идёт долгий и сложный процесс открытия этих концепций — поиск их денотационной семантики и доказательство точных связей между разными концепциями. Computer Science — это именно об этом, и там это не «сложнейшая проблема», а вполне решаемая задача.


  1. beroal
    30.12.2016 14:38

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

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