Кортежи в языках программирования. Часть 1

    Сейчас во многих языках программирования существует такая конструкция, как кортежи (tuples). Где-то кортежи в той или иной мере встроены в язык, иногда — опять же в той или иной мере — реализуются средствами библиотек. C++, C#, D, Python, Ruby, Go, Rust, Swift (а также Erlang, F#, Groovy, Haskell, Lisp, OCaml и многие другие)…
    Что же такое кортеж? В Википедии дается достаточно точное определение: кортеж — упорядоченный набор фиксированной длины. Определение хоть и точное, но для нас пока бесполезное, и вот почему: задумывается ли большинство программистов, зачем понадобилась эта сущность? В программировании существует множество структур данных, как фиксированной, так и переменной длины; они позволяют хранить различные значения — как однитипные, так и разных типов. Всевозможные массивы, ассоциативные массивы, списки, структуры… зачем еще и кортежи? А в языках со слабой типизацией — и тем более, разница между кортежами и списками/векторами совсем размытая… ну нельзя добавлять в кортеж элементы, ну и что с того? Это может ввести в некоторое заблуждение. Поэтому стоит копнуть глубже и разобраться, зачем же на самом деле нужны кортежи, чем они отличаются от других языковых конструкций, и как сформировать идеальный синтаксис и семантику кортежей в идеальном (или близком к идеальному) языке программирования.

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

    Первая важная вещь, о которой на Википедии не упомянули: кортеж — это структура времени компиляции. Иными словами, это некая сущность, объединяющая некоторые объекты на этапе компиляции. И это очень важно. Кортежи неявно используются во всех языках программирования, даже в Си и Ассемблере. Давайте поищем их в том же Си, С++, в любом компилируемом языке.
    Так, список аргументов функции — это кортеж;
    Список инициализации структуры или массива — это тоже кортеж;
    Список аргументов шаблона или макроса — тоже кортеж
    Описание структуры, и даже обычный блок кода — это тоже кортеж; только элементами его являются не объекты, а синтаксические конструкции.

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

    Начнем с самого очевидного — возврата нескольких значений из функции. Еще со школьных времен меня удивляла такая несправедливость: почему функция может принимать сколько угодно значений, а возвращать только одно? В самом деле, почему y=x*x — нормальная парабола, а y = sqrt(x) — какая-то обрезанная наполовину фигня? Разве это не нарушение математической гармонии? В программировании конечно можно возвратить структурный объект, но суть остается та же: возвращается один объект, а не несколько.

    Непосредственная реализация множественного возврата есть в Go. Функция может явно вернуть несколько значений. Синтаксис позволяет присвоить эти несколько значений нескольким переменным, а также выполнять групповые присваивания и даже перестановки аргументов за одну операцию. Однако, никаких других групповых действий кроме присваивания не предусмотрено.
    func foo() (r1 int, r2 int) {
         return 7, 4
    }
    x, y := foo()
    x, y = 1, 2
    x, y = y, x

    Интересная возможность, на которую следует обратить внимание — «пакетная» передача нескольких возвращаемых значений одной функции в другую функцию.
    func bar(x int, y int) {
    }
    bar(foo())


    Такая пакетная передача сама по себе крайне интересна. С одной стороны, она кажется весьма элегантной; но с другой — она слишком «неявная», неуниверсальная. Например, если попытаться добавить в bar третий аргумент, и попытаться совместить «пакетную» передачу и обычную
    bar(foo(), 100)

    то ничего не получится — ошибка компиляции.

    Еще один интересный аспект — неиспользование возвращаемых значений. Вспомним С/С++. В них (а также в подавляющем большинстве других языков — Java, C#, ObjC, D...) можно было спокойно игнорировать возвращаемые значения при вызове функции. В Go такое тоже возможно, причем можно игнорировать как единственное возвращаемое значение, так и группу. Однако, попытка использовать первое возвращаемое значение и неявно проигнорировать второе приводит к ошибке компиляции. Игнорировать возможно, но явно — с использованием специального символа "_":
    x, _ := foo()

    Т.е. работает принцип «все или ничего»: можно или игнорировать все возвращаемые значения, или использовать — но также все.

    В Rust имеются схожие возможности. Точно также функции могут возвращать несколько значений; также можно инициализировать ими новые значения. При этом множественное присваивание как таковое отсутствует, возможна только инициализация. Аналогично можно использовать символ "_" для неиспользуемых значений. Аналогично можно игнорировать возвращаемые значения полностью, или получать их также все полностью. Также кортежи можно сравнивать:
    let x = (1i, 2i, 3i);
    let y = (2i, 3i, 4i);
    if x == y {
        println!("yes");
    } else {
        println!("no");
    }

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

    В языке Swift возможности в целом аналогичны. Из интересного — обращение к элементам кортежа по константному индексу через точку; возможность назначать элементам кортежа имена и обращаться к элементам через них.

    let httpStatus = (statusCode: 200, description: "OK")
    print("The status code is \(httpStatus.0)")
    print("The status code is \(httpStatus.statusCode)") 


    Такие кортежи уже близки к структурам, но все-же структурами не являются. И здесь я бы хотел отойти от примеров и перейти к своим собственным мыслям. Разница между кортежами и структурами в том, что кортеж — это не тип данных, это нечто более низкоуровневое; можно сказать — что кортеж — это просто (возможно именованная) группа (возможно именованных) объектов времени компиляции. В этом месте вспомним языки C/С++. Простейшие конструкции инициализации массива и структуры выглядят так:
    int arr[] = {1, 2, 3};
    Point3D pt = {1, 2, 3};

    Обратите внимание, списки инициализации в данном случае вообще идентичны. И тем ни менее, они инициализируют совершенно разные объекты данных. Такое поведение в общем нетипично для типа данных. Но зато это близко к другой интересной фиче, которая иногда (но редко) встречается в языках программирования — структурной типизации. Конструкция в фигурных скобках — типичный кортеж. Кстати, в Си есть именованная инициализация полей структуры (идея кстати весьма похожа на Swift), которую пока так и не протащили в C++17:
    Point3D pt = {.x=1, .y=2, .z=3};


    В С++ пошли немного в другом направлении: ввели понятие " унифицированный синтаксис инициализации и списки инициализации". Синтаксически это те же кортежи, которые можно использовать для инициализации объектов; в дополнение к старым возможностям, унифицированный синтаксис инициализации позволяет передавать объекты в функции и возвращать из функций в виде кортежей.
        Point3D  pt{10,20,30}; // новый синтаксис инициализации
    Point3D foo(Point3D a)
    {
        return {1, 2, 3}; // возвращаем "кортеж"
    }
    foo( {3,2,1} ); // передаем "кортеж" 


    Другая интересная возможность — списки инициализации.Они используются для начальной инициализации динамических структур данных — таких как вектора и списки. Списки инициализации в С++ должны быть однородными, то есть все элементы списка должны быть одного типа. Технически такие списки формируют константные массивы в памяти, для доступа к которым применяются итераторы std::initializer_list. Можно сказать, что шаблонный тип std::initializer_list — специальный определенный на уровне компилятора интерфейс к однородным кортежам (а фактически к константным массивам). Разумеется, списки инициализации можно использовать не только в конструкторах, но и как аргументы любых функций и методов. Думаю, если бы в С++ изначально был бы некий шаблонный тип данных, соответствующий литеральному массиву и содержащий информацию о длине этого массива, он бы вполне подошел на роль std::initializer_list.

    Также в стандартной библиотеке С++ (и в Boost) существуют кортежи, реализованные с помощью шаблонов. Поскольку такая реализация не является частью языка, синтаксис получился слегка громоздким и неуниверсальным. Так, тип кортежа приходится объявлять явно с указанием типов всех полей; для конструирования объектов применяется функция std::make_tuple; для создания кортежа «на лету» (из существующих переменных) применяется другой шаблон — tie, а получение доступа к элементам осуществляется с помощью специального шаблонного метода, который требует константного индекса.
    std::tuple<int,char> t1(10,'x');
    auto t2 = std::make_tuple ("test", 3.1, 14, 'y');
    int myint; char mychar;
    std::tie (myint, mychar) = t1;                            // unpack elements
    std::tie (std::ignore, std::ignore, myint, mychar) = t2;  // unpack (with ignore)
    std::get<2>(t2) = 100;   
    char mychr = std::get<3>(t2);

    В примере применяется распаковка со специальным значением std::ignore. Это в точности соответствует символу подчеркивания "_", применяемому для тех же целей при групповых возвратах из функций в Go и Rust.

    Похожим способом (хотя и упрощенно по сравнению с С++) реализованы кортежи в C#. Для создания используются методы Tuple.Create(), набора шаблонных классов Tuple<>, для доступа к элементам — поля с фиксированными именами Item1… item8 (чем достигается константность индекса).

    В языке D есть достаточно богатая поддержка кортежей. С помощью конструкции tuple можно сформировать кортеж, и — в том числе — осуществить множественный возврат из функции. Для доступа к элементам кортежа используется индексация константными индексами. Также кортеж можно сконструировать с помощью шаблона Tuple, что позволяет создать кортеж с именованными полями.
    auto t = Tuple!(int, "number", string, "message")(123, "hello");
    writeln("by index 0 : ", t[0]);
    writeln("by .number : ", t.number);
    writeln("by index 1 : ", t[1]);
    writeln("by .message: ", t.message);


    Кортежи можно передавать в функции. Для этого применяется индексация с диапазоном. Синтаксически это выглядит как будто передается один аргумент, а на самом деле кортеж раскрывается сразу в несколько аргументов. При этом в D, в отличие от Go, нет требования точного равенства количества аргументов функции и элементов кортежа, то есть можно смешивать передачу одиночных аргументов и кортежей.
    void bar(int i, double d, char c) { }
    auto t = tuple(1, "2", 3.3, '4');
    bar(t[0], t[$-2..$]);

    В D существует еще множество возможностей, связанных с кортежами — Compile-time foreach для обхода кортежей на этапе компиляции, шаблон AliasSeq, оператор tupleof… в общем все это требует отдельной большой статьи.

    А напоследок рассмотрим реализацию кортежей в малоизвестном расширении языка Си — CForAll или C∀ (забавно, но на момент написания статьи я не смог нагуглить сайт языка — весьма вероятно что он давно закрылся и упоминаний просто не осталось; вот поэтому я регулярно сканирую сеть в поисках новых языков программирования и скачиваю все до чего смогу дотянуться).

    Кортежи в C∀можно объявить на уровне языка, заключив список объектов в квадратные скобки. Тип кортежа создается аналогично — в квадратные скобки заключается список типов. Объекты и типы кортежей можно объявлять явно. Кортежи можно передавать в функции, где они разворачиваются в списки аргументов (в отличие от Go, где такое возможно только при точном совпадении кортежа и списка аргументов функции).
    [ int, int ] w1; // объект-кортеж из двух элементов
    [ int, int, int ] w2; // объект-кортеж из трех элементов
    void f (int, int, int); // функция, принимающая три аргумента
    
    f( 1, 2, 3 ); // просто числа
    f( [ 1, 2, 3 ] ); // кортеж-объект объявленный прямо на месте
    f( w1, 3 ) // кортеж и число
    f( w2 ) // кортеж-объект

    Еще одна интересная тема — вложенные кортежи и правила их раскрытия. В С/С++ также применяется вложенность — при инициализации массивов структур, элементы которых также являются массивами и структурами. В C∀ существуют правила, называемые «tuple coercions», в частности — раскрытие кортежей с внутренней структурой (flattering) и наоборот, структурирование (structuring), когда «плоский» кортеж подстраивается под сложную внутреннюю структуру (хотя эта возможность весьма спорная, обсуждение будет в следующей части). И все это относится лишь к присваиванию, никаких упоминаний использования этих возможностей с другими операциями нет.
    [ a, b, c, d ] = [ 1, [ 2, 3 ], 4 ];

    В C∀ предусмотрено как групповое, так и множественное присваивание.
    [ x, y, z ] = 1.5;
    [ x, y, z ] = [ 1, 2, 3 ];

    и даже использование кортежей для доступа к полям структур
    obj.[ f3, f1, f2 ] = [ x, 11, 17 ];

    Ввиду отсутствия компилятора проверить все эти возможности на практике не удалось, но это безусловно отличная пища для размышлений. Собственно, этим размышлениям и будет посвящена следующая часть статьи.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 54

      +1
      Ах perl какой же он древний что про него забыли.
      А ведь все то вы написали в нем уже есть.
        +2
        Я не писал про «интерпретируемые» языки (Perl, Python, Ruby, PHP и т.д.), потому что там грань между «компиляцией» и «выполнением» несколько мягче. Можно конечно написать и про них еще статью) Ну или может это кто нибудь сделает, кто лучше с этими языками знаком.
        +3
        А откуда информация, что «кортеж — это структура времени компиляции»?
        Сходу источник не смог нагуглить.
          –1
          Это следует, скажем так, из практики программирования и понимания того как все устроено изнутри. Не уверен что это гуглится… ну вот теперь будет гуглиться)
          В книгах и статьях по тому или иному языку программирования обычно дается практическая сторона: делайте вот так — и вы получите вот такой результат, скорее разместите программу в аппсторе и заработаете $$$. Рассуждения о том, что является структурами времени компиляции или времени выполнения, там излишни. Просто говорится, что в кортеж, также как в структуру (struct) например не добавить новые элементы во время выполнения, а в список (list) — можно.
            +5
            В Haskell ни в список ни в кортеж не добавить элементов во время выполнения, но они от этого не становятся одним и тем же
              +4
              То, что в кортеж нельзя добавить новые элементы, не делает его структурой времени компиляции, а лишь неизменяемой структурой данных. Есть много языков, в которых ни один экземпляр любого типа нельзя изменить во время выполнения. Но это не повод называть тот же list структурой времени компиляции.
              Допустим, у нас есть кортеж {x, y}, если его конкретное значение может быть всегда вычислено на этапе компиляции, то да, это структура времени компиляции. Но если какой-то внешний фактор, например пользовательский ввод, может повлиять на значение x или y, то это уже совсем не структура времени компиляции.
                +2
                По мне, кортеж — более глубокое понятие, чисто математическое. Например, в реляционной алгебре есть понятие кортежа, и оно по некоторым параметрам сходно — например, строка из БД — это кортеж — совокупность полей. Хотя здесь речь вообще о компиляции не идет, это чисто абстрактное понятие.
                  0
                  Существование математического понятия с этим названием никак не мешает существованию программистского.
                +1
                Собственно из этого утверждения «кортеж — это структура времени компиляции» и исходят все ваши рассуждения в статье.
                Это утверждение изначально неверное, даже в той же вики вы можете прочитать «Ordered pairs are also called 2-tuples», а уж пара никак не структура времени компиляции.
                  0
                  Списки гомогенны, кортежи гетерогенны. Отсюда вся пляска с фиксацией размера кортежа и прочие вытекающие.
                  И да, мы можем добавлять элементы в кортеж, получая новый экземпляр с новым типом (смотреть гетерогенные списки)
                    0
                    Т.е. получая новый кортеж.
                    Конечно, мне стоило отметить что речь идет в первую очередь о классических компилируемых языках. Чисто функциональные языки я не рассматривал, в них вообще всё по другому, и в первую очередь все данные иммутабельны.
                      0
                      Да в любом случае, вы рассматриваете кортежи в отрыве от типов, что в корне не верно в языке со статической типизацией, будь то императивная сишка или функциональный хк.
                    • UFO just landed and posted this here
                        0
                        Что наоборот?
                        • UFO just landed and posted this here
                            +2
                            Он все правильно сказал. Списки гомогенны, т.к. List<Integer> или List<String>. А вот кортежи гетерогенны, т.к. (Integer, String, Double). Вся суть в типах элементов.
                              0
                              Вообще-то автор не Си обсуждает, а вообще…
                              Берём так любимый нами AppleScript и его списки:
                              set exampleList to {213.1, 6, «Julia, the actress», 8.5}
                              Обмен значений двух переменных:
                              set {a, b} to {b, a}
                              Это всегда был «списко-ориентированный» язык, и никакими кортежами тут мне голову не дурили.

                              Берём другой язык, списки значений в 1С:
                              Список.Добавить(1980, «Год рождения Васи»);
                                0
                                Ну так и в java можно сделать List<Object>. Вопрос лишь в том, как этим потом пользоваться.

                                Ну и если на то пошло, то, имхо, обсуждать различные типы на примере языков со слабой динамической типизацией — это, как минимум, странно.
                  0
                  Кортеж, на самом деле, может быть достаточно неудобен, потому что элементы в нём имеют неосмысленные имена вида «первый», «второй», «тот», «этот» и «ещё один». Чтобы было больше смысла, лучше пользоваться структурами с именованными полями. А если имеется в виду порядковая нумерация элементов — для этого есть массивы.
                    +2
                    Несколько позабавило то, что рассмотрение кортежей идет в отрыве от тех языков, в которых они действительно используются, скажем так, широко… Haskell, Erlang тот же и т.п.

                    Ну и да… повеселило даже не про «времени компиляции», а «кортеж — это не тип данных» :-)
                      –2
                      Смотря что понимать под типом данных.
                      Я бы сказал, что кортежи свободно приводятся к типам данных, но все-же не являются полноценными типами. Например
                      {1, 2, 3}

                      — это что? Массив из трех целых? Структура из трех целых? Список (list)? Множество (set)? Что-то еще?
                        0
                        — это что? Массив из трех целых? Структура из трех целых? Список (list)? Множество (set)? Что-то еще?

                        :-) Это, например, _кортеж_ {int, int, int}.

                        Ну и — на в скидку — к какому _типу данных_ «свободно приводится» какой-нибудь {string, int, boolean}?
                          0
                          К соответствующей структуре, разумеется.
                            0
                            А точнее — к соответствующим по типам полей структурам. Самих структур может быть бесконечно много, и все они по умолчанию не будут совместимы между собой (номинативная типизация).
                              0
                              Вот именно. А _кортеж_ {string, int, boolean} — от такой один единственный.

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

                              Просто, даже ваше утверждение «к соответствующим по типам полей структурам» — оно, мягко говоря, не совсем корректное же. Каким образом вы на уровне _структур_ собираетесь различать {string, int, boolean} и {boolean, int, string}, например?
                                0
                                Я имею в виду примерно вот это:
                                struct Foo {
                                  string str;
                                  int i;
                                  boolean b;
                                }
                                struct Bar {
                                  string name;
                                  int k;
                                  boolean c;
                                }
                                
                                Foo f;
                                Bar b;
                                f = b; // error
                                b = f; // error
                                f = {"hello", 10, true}; // ok
                                b = {"hello", 10, true}; // ok
                                {string, int, boolean} t1 = {"hello", 10, true}; // ok
                                {string, int, boolean} t2 = f; // ok
                                {string, int, boolean} t3 = b; // ok
                                
                                  0
                                  Мне кажется, что пример — надуманный, потому что в первых двух структурах у вас именнованные поля (причём разными именами), а в коде — безымянные.
                                    0
                                    Пример вполне реальный. В структурах именованные поля, и дальше показано что даже если две структуры «структурно идентичны», они все равно несовместимы — то есть объект типа Foo нельзя присвоить объекту типа Bar, и наоборот. То есть первую часть (до кортежей) вы можете проверить практически на любом языке программирования.

                                    Дальше показано, что поведение кортежей отличается от поведения структур — если два кортежа структурно идентичны, то их можно свободно присваивать между собой, и даже между объектами кортежа и структуры. Ближайший аналог подобного синтаксиса — Swift, возможно что-то сделают в C++17.
                                      –1
                                      Причём здесь структуры? Объявите сначала тип, потом создайте две структуры этого типа и присваивайте наздоровье. Структуры так и останутся «структурно идентичны». То что в языках есть проблемы вывода типов, это не проблемы структур. Даже более того, вы выносите какую-то непонятную сущность и гвоорите — вот она, да ничего подобного, просто компилятор приводит тип справа к типу слева, с таким же успехом можно причмокивая обсасыватть такой пример:
                                      var
                                      a: real;
                                      begin
                                      a := 10;
                                      end;

                                      10 это, байт, а он, глядите-ка, присваивается дробным числам. От шайтан, что делается, да это ж кортеж одночленный. Тьфу!..
                                        +1
                                        Объявите сначала тип, потом создайте две структуры этого типа и присваивайте наздоровье.

                                        Так задача поставлена уже. Условия иные: у вас есть два типа, они одинаковы структурно, но они — разные сущности. И правил приведения типов для структур — нет. Можно считать, что кортеж — это структура с ограничениями, для которой определена операция приведения, в случае, если все поля — тех же типов и следуют в том же порядке.
                                          0
                                          А я вижу всего лишь данные, как в комментарии выше, где написано число 10 и не вижу никаких отличий поведения от приведённого мной примера.
                                            0
                                            var
                                            a: real;
                                            b: byte;
                                            begin
                                            a := 10;
                                            b := 10;
                                            end;

                                            Ну давайте теперь поговорим о том, что 10 это магическая сущность, которая может являться и целым и действительным одновременно и она вне типов.
                                            Расширяем пример:
                                            var
                                            a: record
                                            i1: real;
                                            i2: real;
                                            end;
                                            b: record
                                            i1: byte;
                                            i2: byte;
                                            end;
                                            begin
                                            a := (i1:10; i2:10);
                                            b := (i1:10; i2:10);
                                            end;

                                            Можно долго с умным видом рассуждать о вездесущности и приводимости (i1:10; i2:10), но де факто это две совершенно самостоятельные строки без каких-либо взаимосвязей, так же и у вас, то что типы там совпали — это совершенно фиолетово, это не одна сущность «кортеж», это два разных набора данных, по случаю совпавших и приводятся они в каждом конкретном случае именно потмоу, что это данные и их тип определён в левой части, а не в самих данных.
                                            Ещё раз повторяю не стоит плодить непонятные сущности там, где их нет.
                                              0
                                              А кстати, что такое «10»? Какого типа? byte, short, word, int, uint, long, float, double?
                                              Иногда у чисел бывают модификаторы, например можно представить что «10i32» — это int32. А без модификаторов? Брать «тип по умолчанию» («int»), как в Си? А правильно ли это?
                                              В действительности правильный подход к литеральным константам такой — они сами по себе на этапе построения синтаксического дерева не полностью типизированы. Типизация происходит позднее, когда выясняется, в каком выражении с какими типами операндов эта константа используется.

                                              Хотя эти вопросы больше касаются дизайна компиляторов, но это не делает их мене интересными. И это открывает дополнительные возможности — такие как архитектурно правильная интеграция в язык программирования «длинной арифметики».
                                                +1
                                                Даже не знаю как до вас достучаться. Не имеет никакого значения какого типа число 10, это была иллюстрация вот к этому примеру:
                                                f = {"hello", 10, true}; // ok
                                                b = {"hello", 10, true}; // ok

                                                Вы обзываете {«hello», 10, true} кортежем, а раз там и там кортеж, то это по вашему одно и то же.
                                                Я же вам наглядно уже не первый пост пытаюсь показать, что {«hello», 10, true} <> {«hello», 10, true}, это совершенно разные данные, то что они выглядят одинаково с точки зрения компилятора, да и логики языка, ни о чём не говорит. Поэтому не имеет значения насколько равны у вас исходные структуры, им присваиваются разные данные.
                                                На этом предлагаю дисскуссию завершить, если вы и тут меня не поняли, то я не вижу смысла далее разъяснять.
                                                  +1
                                                  Даже не знаю как до вас достучаться. Не имеет никакого значения какого типа число 10, это была иллюстрация вот к этому примеру:

                                                  Ключевое в примере выше — вот это:

                                                  Foo f;
                                                  Bar b;
                                                  f = b; // error
                                                  b = f; // error
                                                  

                                                  Если вы этого не понимате — печально.

                                                  Я же вам наглядно уже не первый пост пытаюсь показать, что {«hello», 10, true} <> {«hello», 10, true}, это совершенно разные данные,

                                                  А {string. int, boolean} и {string. int, boolean} — один и тот же тип.
                                                  В отличие от
                                                  struct Foo {
                                                    string str;
                                                    int i;
                                                    boolean b;
                                                  }
                                                  struct Bar {
                                                    string name;
                                                    int k;
                                                    boolean c;
                                                  }
                                                  
                                                0
                                                это не одна сущность «кортеж», это два разных набора данных, по случаю совпавших и приводятся они в каждом конкретном случае именно потмоу, что это данные и их тип определён в левой части, а не в самих данных.

                                                Нет, как раз новая сущность «кортеж» определяет то, что это одинаковые наборы данных. Правила приведения применяются в первую очередь для кортежа, а не его элементов. В итоге вы можете скопом передать, привести набор данных.
                                              +1
                                              Кстати, из смешного. Про кортеж одночленный. Создателям Хаскеля тоже было смешно.
                                              Как потом оказалось, это всё же удобная конструкция для определённых случаев.
                                              И даже добавили внешнюю библиотеку с одночленным кортежем — пакет One Tuple
                                                0
                                                Да что там хаскел. Питон.
                                                Поскольку синтаксис (x,y,z.....) в случае одночленного кортежа превращается в чисто декоративные скобки (x), — специально для этого придумали хак — (x,)
                                          +1
                                          Спасибо… я, кажется, понял ваш ход мыслей.

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

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

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

                                          В принципе, «можно считать» как угодно. Ктож нам запретит :-) Вопрос только: какой в этом смысл?! С тем же успехом я могу утверждать что это структура — это кортеж специального вида. И я даже знаю язык в котором это действительно так :-)
                                            –1
                                            Я вас тоже понял. Конечно, кортеж это тип с определенной точки зрения. И для конечного программиста удобнее рассматривать кортеж именно как тип, обладающий какими-то особенностями — по сравнению например со структурами, которые обладают другими особенностями.
                                            Но я выбрал несколько другую точку зрения, более близкую к внутреннему устройству компилятора, и с этой точки зрения кортеж — скорее "полуфабрикат", заготовка для "полноценного" типа.
                                            Так например, список аргументов функции — это тип? Обычно нет (сама сигнатура функции — тип, но не отдельно список аргументов). Но при этом вполне можно сказать что список аргументов функции — это кортеж. Ну или рассматривать список аргументов функции как самостоятельный тип, что в общем одно и то же.
                                              +1
                                              Но я выбрал несколько другую точку зрения, более близкую к внутреннему устройству компилятора, и с этой точки зрения кортеж — скорее «полуфабрикат», заготовка для «полноценного» типа.

                                              Ну тогда можно как-то по подробнее про "внутреннее устройство компилятора"? И как быть в случаях когда этого самого "компилятора" вообще нет?! Следует ли это понимать так, что в этом случае типы отсутствую как таковые?

                                              Вообще, ваше настойчевое противопоставление "типов компилятора", "типов определенной т.з" и "типов вообще" веселит меня гораздо больше, чем ваше упорство в попытке убедить общественность сначала в том, что "кортеж — не тип", а теперь, надо понимать, в том, что "кортеж — не настоящий тип". Я наверное таки возьму на себя смелость и поинтересуюсь у вас: "а что такое тип?". :-)

                                              Так например, список аргументов функции — это тип? Обычно нет (сама сигнатура функции — тип, но не отдельно список аргументов). Но при этом вполне можно сказать что список аргументов функции — это кортеж.

                                              Что значит "обычно нет"?! Это сильно зависит от определения функционального типа в данной конкретной системе типов. Если в СТ нет функционального типа, то у вас и "сама сигнатура функции" — не тип. Если есть, то все зависит от его определения. И это вполне может быть и не кортеж вообще. "Ф-ции одного аргумента достаточно для всего" (с) :-)
                                                0
                                                Думаю лучше будет если я попробую подробнее разъяснить свою точку зрения в следующей части.
                                                Здесь да, признаюсь это моя ошибка — я сделал утверждение, которое является скорее моей оригинальной идеей, а не чем-то общепринятым, да еще и не объяснил что это за идея и почему я так считаю. Обязательно это учту.
                                              0
                                              В принципе, «можно считать» как угодно. Ктож нам запретит :-) Вопрос только: какой в этом смысл?! С тем же успехом я могу утверждать что это структура — это кортеж специального вида. И я даже знаю язык в котором это действительно так :-)

                                              Это было мое утверждение. Смысл очень простой: есть важное свойство — приводимость, которое позволяет считать, что у нас есть отдельный новый тип — кортеж. Под "можно считать" я имел в виду ближайшую аналогичную конструкцию, и указал это отличие, которое позволяет считать к. отдельным типом.
                              • UFO just landed and posted this here
                                  0
                                     Point3D  pt{10,20,30}; // новый синтаксис инициализации
                                  Point3D foo(Point3D a)
                                  {
                                      return {1, 2, 3}; // возвращаем "кортеж"
                                  }
                                  foo( {3,2,1} ); // передаем "кортеж" 
                                  


                                  Вот за это отдельное спасибо!
                                    +2
                                    Вообще кортежи ИЗОМОРФНЫ(равнозначны) структурам.
                                    Полностью.
                                    Отличия следующие
                                    1) Тип структуры вначале декларируется, потом используется. В то время как тип кортежа вычисляется на лету.
                                    2) Кортеж — это структура с безыменным доступом до полей. Или же структура — это кортеж с именным доступом до полей.
                                    3) Технологически построение кортежей и структур в языке может отличаться

                                    Вообще странно, что вы не упомянули примером ни один функциональный язык, ибо кортежи/туплы в основном используются там. И оттуда перекочевали в Go и Rust
                                      0
                                      В Swift поля кортежа могут иметь имена.
                                      И кстати, там интересные правила присваивания кортежей с именованными полями, о чем я не рассказал…
                                      Кортеж с безымянными полями можно присвоить любому с именованными, и наоборот.
                                      А если оба кортежа с именованными полями, то присваивание возможно только при совпадении имен полей.

                                      Из того что я еще не рассмортел — Scala, а тоже стоило бы:)
                                      +1
                                      Ну и «чтобы два раза не вставать» Бьерн Страуструп на днях (буквально вчера) предложил расширения C++ для работы с кортежами isocpp.org/blog/2016/02/a-bit-of-background-for-the-structures-bindings-proposal
                                        0
                                        Из всей статьи я так и не понял, чем отличаются списки от кортежей.
                                        Более того, автор говорит о языках программирования и его несёт в структуры данных SQL, при этом в процедурных языках данные и код это совершенно разные вещи. Либо давайте конкретно обсуждать языки ориентированные на данные типа HTML (не вижу смысла в контексте кортежей), либо уже плавно съезжайте на объектные которые хоть и имеют парадигмы, но де факто обращаются с данными так же как процедурные языки. К чему компиляция вообще не понятно, я уже приводил пример для расспространённых интерпретаторов языков, таких как AppleScript и 1С . В частности меня интересует объектно ориентированный язык AppleScript, который так же является списко-ориентированным, вот конструкция где на лету создаётся список из двух переменных и обмениваются значения этих переменных:
                                        set {a, b} to {b, a}

                                        Ну а вот как создаётся именнованный список:
                                        set exampleList to {213.1, 6, «Julia, the actress», 8.5}


                                        Я понимаю что такое список, но я так и не понял, что такое кортеж, кроме того, что он везде и — нигде.
                                          0
                                          Можно сказать, что кортежи это «почти что структуры» (struct).
                                          Чем отличаются списки от структур, я надеюсь что понятно:)
                                          Более интересный вопрос — чем отличаются кортежи от структур, там отличия более тонкие (хотя статья не посвящена именно этим отличиям).
                                            0
                                            На сколько я понимаю, единственное принципиальное отличие структуры от кортежа в том, что она умеет рекурсию.
                                              0
                                              Еще структуры как правило номинативно типизированы; то есть объявленная структура создает тип, по умолчанию несовместимый с другими типами, даже если структурно эти типы идентичны.
                                              habrahabr.ru/post/276871/#comment_8769577
                                                +1
                                                Да, и еще поля структуры именованы. Но, имхо, это все уже вторично.
                                                0
                                                Боюсь вас расстроить, но структура, в классическом понимании, не является рекурсивным типом.

                                          Only users with full accounts can post comments. Log in, please.