Объясните мне, как вы для себя разобрались в моделях типизаций — они же все размыты


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


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


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


    Самих параметров типизаций несколько.


    Есть статическая/динамическая типизация. Статическая — это когда типы данных известны на этапе компиляции. Компилятор знает типы переменных, и на их основании проверяет корректность программы. Динамическая — это когда типы известны только на стадии выполнения, и компилятор при сборке не проверяет ничего.


    При этом все статически типизированные ЯП, которые я использовал, дают возможности для динамической типизации. Any в TS, Dynamic в сишарпе. В конце концов тот же тип Object — по идее, я запихиваю в него все что угодно, и говорю, что у меня статически типизированный код. Но строго говоря, это не совсем так. Потому что я могу принять Object извне, и покастить его к чему угодно, не делая никаких проверок — это ли не элемент дин типизации?


    Я не знаю, делает ли язык более динамическим возможность динтипизировать куски кода. Если VSCode статически чекает мой js код, в котором даже определений типов нет — делает ли это джаваскрипт статически типизированным? А если да, то как тогда это ужать в бинарный параметр?


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


    Когда я начинал писать на тайпскрипте, я знал, что у него статическая типизация. Тогда я не понимал, что это значит, и думал что статическая — это как в сишарпе. Я называл её строгой. Я писал свой код на ts, и не понимал — какого хрена происходит. Почему я объявил тип


    type Lead = {
      id: string
    }

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


    Номинативная — это когда для нас значимо имя типа, его номинал. А структурная — это когда мы проверяем типы не по именам, а по их свойствам. То-есть, если у меня есть вот такие классы


    class A {
     string name;
    }
    
    class B {
     string name;
    }

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


    Дело в том, что теоретически номинативная даёт больше гарантий. Но это сложные гарантии. Если я написал функцию, которая работает с классом А из примера, то и с инстансом класса B она отработает корректно — в том смысле, что она не свалится на этапе исполнения с ошибкой. Поэтому, когда я программирую на языке со структурной типизацией, я пишу функцию, и говорю — вот она работает с чем угодно, у чего есть вот такие поля или методы.


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


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


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


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


    type A = {
      id: string
    }
    
    type B = {
      id: string;
      name: string;
    }
    
    function test(a: A) {}

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


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


    Мой друг Паша как то сказал мне, что в тайпскрипте типизация — дерьмо. Я ответил, что в тайпскрипе я могу написать тип, который описывает число меньше ста, и получу компайл тайм гарантию, что число было проверено. А в сишарпе я так сделать не смогу. Паша впечатлился. Но он тоже прав — в куче кейсов типизация в ts — дырявая.


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


    Понятие очень размытое, например считающийся строгим сишарп позволяет программисту описать имплиситный каст например Собаки к инту. За 7 лет моей работы с дотнет стеком я ни разу не встречал кода, который дефайнил бы имплиситные касты — к чести моих коллег — но сам язык это позволяет.


    Для меня этот параметр не особо принципиален — наверно потому, что совсем уж слабо типизированнах япов на рынке нет.


    Когда меня спрашивают, сильная ли типизация в C# я говорю да. А когда спрашивают то же самое про тайпскрипт, я говорю нет. Но на деле, оба этих языка содержат имплиситные касты, и большой вопрос, в каком их больше. Я не знаю, какой тут алгоритм — как определить, сильная или строгая типизация у того или иного языка. Я даже не знаю, какая она должна быть, если честно. Думаю, меня вполне устроит, если функция, которая ждет строку, получит число и скастит его к строке, и думаю, меня совсем не устроит, если компилятор скастит к строке например boolean.


    Есть ещё один параметр типизации, для которого я не знаю подходящего термина. Допустим у меня есть вот такой тип


    type Person = {
      id: string;
      name: string;
    }

    Есть языки программирования, которые потащат в рантайм метаинформацию этого типа. Например C# позволяет на этапе исполнения посмотреть, какие поля и каких типов есть у такого-то класса. Это важная вещь — так я могу автоматизировать валидацию сторонних данных. А вот в тайпскрипте вся информация о типах есть только на этапе компиляции. Я раньше путал этот параметр с номинативной/структурной, но по факту я могу себе представить структурный яп, который тащит метаинформацию в рантайм.


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


    Это навело меня на мысль, что весь вопрос в комбинации этих параметров.


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


    Более того, команда разработки тайпскрипта по сути работает только над статическим анализом — и это позволяет им сделать его по-настоящему мощным. Во всяких сишарпах, джавах, котлинах и т.д. невозможно сделать на этапе компиляции вещи, которые для тайпскрипта обыденность. Я имею в виду магию с conditional и mapped types. Я никогда не достигну такого уровня проверки корректности на этапе компиляции в C#.


    Но есть гигантский минус. При кодировании на тайпскрипте система типов защищает код на тайпскрипте от кода на тайпскрипте — и никак не помогает с внешними данными. Получается, что если я описал тип User, и получил список таких юзеров из JSON, мне придётся руками перечислять все свойства, и делать все проверки. Это объективно — говно.


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


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


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


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


    Что ещё больше размывает границы всех этих типизационных терминов.


    Вот F# — номинативно типизированный ЯП. Но. Но. В нем можно написать функцию, которая работает с чем угодно, у чего есть такое-то поле, такого-то типа. Более того, я могу весь свой код писать в таком стиле — это будет структурно типизированный код на номинативно типизированном F#. Как с этим быть?


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


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


    Смотрите мой подкаст

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 435

      +30

      За долгое время поставил автору плюс за статью. Наконец то не про "боль и страдания короля", а про что-то интересное с технической точки зрения.

        +2

        Да, интересно и раскрыта объемная тема в простых словах. Но для меня главный вопрос — куда делась предыдущая статься про F#?

          +6
          Забрал переписывать, что бы донести до вас то же самое посильнее
        +3
        Это навело меня на мысль, что весь вопрос в комбинации этих параметров.

        Угу. Хотя можно представить себе язык, отдающий эти решения программисту:

        type A = { name: string } // структурный тип, вместо него можно передать экземпляр B
        nominal type B = { name: string } // номинальный тип, вместо него нельзя передать A
        

        Насколько программист будет рад каждый раз делать этот выбор — неясно.
          0
          Насколько я помню, так есть в OCaml. Есть специальные структуры данных, которые номинативные, а есть структурные
            +1
            Спасибо, не знал.
              +1

              Анонимные рекорды в F# пытаются играть по примерно таким же правилам, кстати.

                +1

                Уточню. В окамле несколько концептуально разных механизмов, при этом заметно пересекающихся по возможностям. Есть записи (records), и они номинативно типизированы, несмотря на то что выглядят как структуры: https://ideone.com/8mQqs7. Есть объекты с методами, и они типизированы структурно: https://ideone.com/GS3bVF. А ещё есть модули, у которых есть свои вроде-типы (сигнатуры) и вроде-функции (функторы), и они тоже типизированы структурно: https://ideone.com/TBiz1F.

                +1

                К слову говоря, Python позволяет так делать. Можно использовать аналог номинального типа Protocol, который проверяет объект на соответствие методам, атрибутам. А можно использовать обычный класс или тип, который ведёт себя классическим образом.


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


                Но вообще спасибо за статью, понял глубину задумки аннотации типов Python. Какая мощная штука оказалась.

                0
                Вот F# — номинативно типизированный ЯП. Но. Но. В нем можно написать функцию, которая работает с чем угодно, у чего есть такое-то поле, такого-то типа.

                а чем вам generic классы не угодили?
                  +1
                  А где я написал, что они мне не угодили?
                    +1
                    Так в си шарпе тоже можно создать проперти любого(T) типа. А еще есть тип dynamic которому можно присвоить все что угодно, другой вопрос, нужно ли это, потому-что работать это будет примерно со скоростью JavaScript
                      +3
                      Так. Это все верно конечно, но как это противоречит тому что написал я?
                        +2

                        Но где написано, что это противоречит? :)

                          +2
                          А где, что нет?)
                            +1
                            Прикольная у вас рекурсия получилась.
                    0

                    в C# нельзя сделать вторую часть, а именно структурную типизацию(наличие поля/метода).
                    Я одно время даже думал из-за этого писать на F# немного, так как там я могу написать универсальный тип Vector.

                      +2
                      Но в C# есть специальные костыли, например методы Add, Dispose, Deconstruct и т.д. Получается в некоторых специальных местах он структурно типизированный.
                        0

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


                        Вон ниже, как раз пример привели

                        +2
                        В C# получается как то так:
                        public class A : IA 
                        {
                          public string id { get; set; }
                        }
                        
                        public class B : IA
                        {
                          public string id { get; set; }
                          public string name { get; set; }
                        }
                        
                        public interface IA
                        {
                          public string a { get; set; }
                        }
                        
                        public void test(IA a) {}
                          0

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


                          Этот комментарий показывает примеры чего шарп не может.

                          0

                          У С# же есть интерефейсы. Разве нельзя ограничивать по ним?

                            +1
                            Но тогда в классе должна явно быть заявлена реализация интерфейса. Т.е., уже существующий сторонний класс, соответствующий нашему интерфейсу, но не объявленный как реализующий его, мы использовать не сможем.
                              +2
                              Сделать наследника от этого класса + навесить интерфейс?
                              +1
                              В Go, если не ошибаюсь, как раз такая(на мой взгляд, довольно удобная) реализация интерфейсов — структура считается реализующей интерфейс, если у неё есть метод\методы соответствующие сигнатурам интерфейса, т.е. явное объявление не нужно. При этом, сами структуры номинативные.
                                0

                                Тут принципиальна не неявность реализации интерфейса, а отделение описания типа от реализации интерфейса для типа. И Go в этом плане не оригинален: такое же разделение, но с явной реализацией, есть в Rust, Swift, Scala, Ocaml, Haskell и древнем StandardML — и это только те, о которых мне известно.

                                  0
                                  Хм, всё больше желания потыкать Rust.
                                0

                                Что-то вроде такой ситуации?


                                interface Shape {
                                int area();
                                }
                                class Square: Shape { // тут интерфейс
                                  int area(){ /*impl*/}
                                }
                                class Circle { // а тут нет интерфейса
                                  int area(){ /*impl*/}
                                }
                                  +1

                                  Да, при этом class Circle у вас где-то в libcircles.dll и запатчить туда свой интерфейс нет возможности.

                            +1
                            а чем вам generic классы не угодили?

                            тем что в C# нельзя написать такой метод
                            static T Sum<T>(T x, T y): where T: operator (+) => x + y;
                            который бы работал для любых типов, у которых объявлен статический оператор сложения.


                            А в F# можно

                              +2
                              А вы часто пишите код с операторами? Я вот за восемь лет работы, только в книжках видел как оператор использовать, на практике не разу не пригодилось, как и goto
                                +2
                                в F# часто операторами играют
                                  0
                                  посмотрел зп F# разрабов у нас в фирме(вакансии), надо что-ли освоить его=)
                                  +5

                                  Это не только про операторы.
                                  например такое возможно в фшарпе.
                                  Функция, которая достаёт из любого типа поле Id без навешивания на тип интерфейсов.


                                  static string GetId<T>(T entity)`
                                      : where T: string Id { get; } => 
                                      entity.Id;
                                    –1
                                    Через рефлексию можно и в сишарпе.
                                      +2

                                      Это можно сразу исключить. Рефлексия не является zero-cost abstraction и она не проверяется статически.


                                      В фшарпе такая функция неотличима в коде от вызова проперти (то есть zero-cost) и в неё нельзя сунуть тип, у которого нет такого свойства (т.к. это ограничение на тип).

                                  +2
                                  Кстати уже давно висит вот такой feature request в C#: Exploration: Shapes and Extensions. С таким нововедением ваш Sum тоже можно будет писать на C#, правда с лишними телодвижениями. Раньше проскакивала информация что это планируют добавить в C# 9.0, но к сожалению на данный момент это не в списке candidates.
                                    0

                                    См. ниже. При желании всегда можно написать руками, и выглядит не очень ужасно. Что до автоматического генерирования структурных тайпклассов — ну… Не знаю, насколько это хорошая идея. Мне кажется, что это не очень полезно.

                                    +1

                                    Есть классический способ энкодить тайпклассы в сишарпе:


                                    void Main()
                                    {
                                        var numbers = new[] { 1, 2, 3, 4, 5 };
                                        var strings = new[] {"Hello", " ", "World" , "!"};
                                        Console.WriteLine(MConcat<int, IntSGroup>(numbers));
                                        Console.WriteLine(MConcat<string, StringSGroup>(strings));
                                    }
                                    
                                    public interface SGroup<T> // наш тайпкласс
                                    {
                                        public T Zero { get; }
                                        public T Add(T x, T y);
                                    }
                                    
                                    public struct IntSGroup : SGroup<int> // реализации для наших типов - привет, имплиситы
                                    {
                                        public int Zero => 0;
                                        public int Add(int x, int y) => x + y;
                                    }
                                    
                                    public struct StringSGroup : SGroup<String>
                                    {
                                        public String Zero => "";
                                        public string Add(string x, string y) => x + y;
                                    }
                                    
                                    // пример абстрактного кода, работающего с тайпклассами
                                    public static T MConcat<T, TGroup>(IEnumerable<T> items) where TGroup : struct, SGroup<T> {
                                        var typeclass = default(TGroup);
                                        return items.Aggregate(typeclass.Zero, typeclass.Add);
                                    }

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

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

                                  Хаскель не делает, например. Что интересно, куда более строгий идрис позволяет зарегистрировать функции, которые будут использоваться для кастов.


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


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

                                  Нет, конечно. Чем меньше притащено в рантайм, тем быстрее всё работает. Да и если вы типы проверили, то зачем вам эта информация в рантайме?


                                  Но при этом неплохо иметь способ попросить компилятор сгенерировать информацию о типах, да, когда это изредка бывает нужно (как тот же Typeable в хаскеле).


                                  Вот F# — номинативно типизированный ЯП. Но. Но. В нем можно написать функцию, которая работает с чем угодно, у чего есть такое-то поле, такого-то типа. Более того, я могу весь свой код писать в таком стиле — это будет структурно типизированный код на номинативно типизированном F#. Как с этим быть?

                                  Есть такая тема, как row polymorphism, и это очень клёвая тема. Не знаю, есть ли она в F# (я не знаю F#), но в этом несчастном хаскеле её очень не хватает.

                                    +1
                                    Нет, конечно. Чем меньше притащено в рантайм, тем быстрее всё работает. Да и если вы типы проверили, то зачем вам эта информация в рантайме?


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

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

                                        +1
                                        Можно пример?
                                          0

                                          https://en.wikipedia.org/wiki/Tagged_union#1970s_&_1980s


                                          enum ShapeKind { Square, Rectangle, Circle };
                                          
                                          struct Shape {
                                              int centerx;
                                              int centery;
                                              enum ShapeKind kind;
                                              union {
                                                  struct { int side; };           /* Square */
                                                  struct { int length, height; }; /* Rectangle */
                                                  struct { int radius; };         /* Circle */
                                              };
                                          };
                                            +2
                                            Я просил пример паттерн матичнга по типам, который сможет работать без информации о типах
                                              0

                                              Насколько я понимаю, хаскеллевские алгебраические типы компилируются во что-то подобное. 0xd34df00d, вероятно, не считает kind информацией о типе

                                                +2
                                                Но это она и есть же
                                                +1

                                                Стоп, вы про паттен-матчинг по типам? А в каком языке (кроме Idris 2) вы это можете делать? В хаскеле по типам вы матчиться не можете.


                                                А я, если что, говорил о


                                                data Foo = F1 | F2 Int | F3 Bool
                                                data Bar = B1 Double | B2 Foo
                                                
                                                f1 :: Foo -> Bool
                                                f1 F1 = ...
                                                f1 ...
                                                
                                                f2 :: Bar -> Int
                                                f2 B1 = ...
                                                f2 ...

                                                Обе функции могут смотреть в условный int по адресу, на который указывает аргумент. Для первой функции значение 0 будет означать F1, а для второй то же самое значение может означать B1. Где ж тут информация о типе? Она тут только о номере конструктора.

                                                  0
                                                  А в каком языке (кроме Idris 2) вы это можете делать?

                                                  C#

                                                    0

                                                    А, ну там что угодно может быть. А, кстати, как там паттерн-матчинг по типам выглядит?

                                                      +6
                                                      public static double ComputeAreaModernSwitch(object shape)
                                                      {
                                                          switch (shape)
                                                          {
                                                              case Square s:
                                                                  return s.Side * s.Side;
                                                              case Circle c:
                                                                  return c.Radius * c.Radius * Math.PI;
                                                              case Rectangle r:
                                                                  return r.Height * r.Length;
                                                              default:
                                                                  throw new ArgumentException(
                                                                      message: "shape is not a recognized shape",
                                                                      paramName: nameof(shape));
                                                          }
                                                      }
                                                      


                                                      Пример отсюда
                                                        +5

                                                        Любопытно, спасибо!


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

                                                          +1
                                                          Согласен: если речь идёт о функции, которая принимает object и сверяет его тип на соответствие конкретным типам, такой код явно пахнет.

                                                          Но в случае, например, Union-типов, когда на входе у нас, например, `Customer | Error`, можно обработать ошибочный сценарий в функциональном стиле при помощи паттерн-матчинга. Эх, вот бы наконец завезли в C# Union-типы…
                                                            +1
                                                            Кстати, в фп япах то обычно нет юнионов — там размеченные юнионы, т.е.

                                                            type A = B of int | C of int

                                                            и собственно вот этот размеченный лейбл и едет в рантайм. И по нему же, наверное, паттерн матчинг и работает
                                                              +2
                                                              Дык собственно в «серьёзных языках» изначально были именно тегированные Union'ы. В том же ALGOL, Pascal (в том, что Вирт придумал, конечно, а не в том, что Хейлсберг реализовал).

                                                              В Fortran, впрочем, были COMMON-блоки, которые позволяли калбмур типизации делать.

                                                              А потом — случилась революция.

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

                                                              А сейчас — вообще период двоемыслия. Когда все менеджеры «бьют себя пяткой в грудь» и публично объясняют что борьба с ошибками — это очень важно и нужно… а потом устраивают общения с разработчиками один-на-один и пытаются сделать так, чтобы «таски» как можно быстрее закрывались. «А если ошибки возникнут — так мы их потом пофиксим».
                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                              0
                                                              Эх, вот бы наконец завезли в C# Union-типы…

                                                              Переходите на PHP — к нам завозят в этом году :)

                                                              0
                                                              ФП-языки очень боятся давать возможность делать паттерн-матчинг по типам, потому что это ломает параметричность

                                                              Нет, не поэтому. Просто для паттерн-матчинга по типам нужно для начала ввести в язык подтипы и полиморфизм подтипов.


                                                              А параметричность вы сами ломали когда делали Has на дженериках, и никто из-за этого не умер...

                                                                +1
                                                                Нет, не поэтому. Просто для паттерн-матчинга по типам нужно для начала ввести в язык подтипы и полиморфизм подтипов.

                                                                Вовсе нет:


                                                                notId : {a : Type} -> a -> a
                                                                notId {a = Nat} _ = 0
                                                                notId {a = Bool} b = not b
                                                                notId n = n

                                                                даёт


                                                                % idris2
                                                                     ____    __     _         ___
                                                                    /  _/___/ /____(_)____   |__ \
                                                                    / // __  / ___/ / ___/   __/ /     Version 0.2.0-4aa1c1f44
                                                                  _/ // /_/ / /  / (__  )   / __/      https://www.idris-lang.org
                                                                 /___/\__,_/_/  /_/____/   /____/      Type :? for help
                                                                
                                                                Welcome to Idris 2.  Enjoy yourself!
                                                                Main> :l Dyna.idr
                                                                1/1: Building Dyna (Dyna.idr)
                                                                Loaded file Dyna.idr
                                                                Main> notId (the Nat 10)
                                                                0
                                                                Main> notId False
                                                                True
                                                                Main> notId (the Int 10)
                                                                10
                                                                Main> notId "meh"
                                                                "meh"
                                                                Main> :total notId
                                                                Main.notId is total

                                                                Паттерн-матчинг? Да. По типам? Да. Без универсумов? Да.


                                                                А параметричность вы сами ломали когда делали Has на дженериках, и никто из-за этого не умер...

                                                                Эм, почему же? Это всего лишь рефлексия. Рантайм-поведение не отличается от того, как если бы я писал все нужные инстансы руками.

                                                                  0

                                                                  Ну, я писал про паттерн-матчинг переменной по типу, а не типа по типу. Если начать считать вторые случаи — то в список языков с паттерн-матчингом по типам надо внести С++ (специализация шаблонов работает как паттерн-матчинг), Rust (реализации трейтов) и Haskell (инстансы тайпклассов, ограниченно). Даже странно, что вы не припомнили ни одного из этих языков :-)




                                                                  Эм, почему же? Это всего лишь рефлексия. Рантайм-поведение не отличается от того, как если бы я писал все нужные инстансы руками.

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


                                                                  foo :: a => (a, Bar) -> Bar
                                                                  foo = extract

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

                                                                    0
                                                                    Если начать считать вторые случаи — то в список языков с паттерн-матчингом по типам надо внести С++ (специализация шаблонов работает как паттерн-матчинг)

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


                                                                    Rust (реализации трейтов) и Haskell (инстансы тайпклассов, ограниченно).

                                                                    Про раст я знаю недостаточно, а хаскель с тайпклассами — так эти тайпклассы — это всего лишь ещё один параметр функции. Когда вы пишете foo :: TypeClass tc => ..., то вы на самом деле неявно имеете у функции ещё один параметр со словарём функций для tc, и всё. Внутри функции при этом вы паттерн-матчиться на конкретный инстанс тайпкласса не можете.


                                                                    Так вот, для любого a кроме некоторых странных случаев она будет возвращать второй элемент пары, но для пары (Bar, Bar) она вернёт первый.

                                                                    Более того, можно проще без всяких дженериков (пишу из головы, код может не компилироваться, и его надо посыпать всякими там OVERLAPPING, но идея понятна, надеюсь):


                                                                    class Stupid a where
                                                                      stupidId :: a -> a
                                                                    
                                                                    instance Stupid Bool where
                                                                      stupidId = not
                                                                    
                                                                    instance Stupid a where
                                                                      stupidId = id

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


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

                                                                      0
                                                                      Внутри функции при этом вы паттерн-матчиться на конкретный инстанс тайпкласса не можете.
                                                                      Погодите. Вот тут же написано что можете:
                                                                      char x = case cast x of
                                                                                 Just (x :: Char) -> show x
                                                                                 Nothing -> "unknown"
                                                                      

                                                                      Опять же, там матан за пределами моих знаний, но моя ментальная модель такая, что всё ломается, когда вы внутри функции можете сделать паттерн-матчинг по типу.
                                                                      Ментальная модель ломается — но код-то работает!
                                                                        0

                                                                        Typeable — это ну такое. Во-первых, Typeable — это ИМХО плохо пахнущий код. Во-вторых, ваш тип может не поддерживать Typeable, если вы не написали deriving (Typeable). Во-третьих, вы делаете паттерн-матчинг не по типу, а по значению (Typeable сводится к наличию рантайм-метки у объектов).


                                                                        То есть, это эквивалентно классу вроде


                                                                        class RuntimeId a where
                                                                          getId :: proxy a -> Int
                                                                        
                                                                        instance RuntimeId Char where
                                                                          getId _ = 0
                                                                        instance RuntimeId MyType where
                                                                          getId _ = 100500 -- как-то гарантируем уникальность

                                                                        и последующим вещам вроде


                                                                        withRuntimeId :: RuntimeId a => a -> String
                                                                        withRuntimeId _ =
                                                                          case getId (Proxy :: Proxy a) of
                                                                               0 -> "yay got int"
                                                                               1 -> ...
                                                                               100500 -> "yay got my type"

                                                                        Кстати, интересно, что в кишках cast использует unsafeCoerce:


                                                                        eqTypeRep :: forall k1 k2 (a :: k1) (b :: k2).
                                                                                     TypeRep a -> TypeRep b -> Maybe (a :~~: b)
                                                                        eqTypeRep a b
                                                                          | sameTypeRep a b = Just (unsafeCoerce# HRefl)
                                                                          | otherwise       = Nothing

                                                                        а вот это вот sameTypeRep, считайте, сравнивает эти самые метки.


                                                                        Короче, снова функции и словари.

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

                                                                        Ха-ха:


                                                                        notId :: a => a -> a
                                                                        notId = stupidId

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


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

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


                                                                        Ещё она ломается при:


                                                                        • использовании Overlapping instances
                                                                        • использовании Type families (именно так вами была сломана параметричность у extract)

                                                                        В общем, она ломается при любом паттерн-матчинге по типу, вовсе не обязательно внутри функции.

                                                                          0
                                                                          Ха-ха:

                                                                          Не имеете права. Надо написать notId :: Stupid a => a -> a. И вот вам ваш словарь.


                                                                          использовании Overlapping instances

                                                                          С учётом того, что там таки везде нужен дополнительный параметр, описывающий поведение, не уверен.


                                                                          использовании Type families (именно так вами была сломана параметричность у extract)

                                                                          Это всего лишь вычисления на типах, как они могут повлиять на рантайм-поведение в этом смысле?

                                                                            0
                                                                            Не имеете права. Надо написать notId :: Stupid a => a -> a. И вот вам ваш словарь.

                                                                            Неа, не надо. Инстанс instance Stupid a даёт право не указывать такое ограничение, только что проверил.


                                                                            Это всего лишь вычисления на типах, как они могут повлиять на рантайм-поведение в этом смысле?

                                                                            Нарушают "регулярность" системы типов языка. Точно не скажу (теперь уже мне матана не хватает).

                                                                              0
                                                                              Неа, не надо. Инстанс instance Stupid a даёт право не указывать такое ограничение, только что проверил.

                                                                              Да, вы правы.


                                                                              Прикольно, лишний повод говорить о неконсистентности хаскеля.

                                                                                0
                                                                                Неа, не надо. Инстанс instance Stupid a даёт право не указывать такое ограничение, только что проверил.

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


                                                                                Как и интуитивно ожидалось, там надо включить IncoherentInstances (на что в хаскель-сообществе смотрят очень косо, и лично для меня это почти всегда знак, что я делаю что-то сильно не то):


                                                                                Такой код:


                                                                                {-# LANGUAGE FlexibleInstances #-}
                                                                                
                                                                                class Stupid a where
                                                                                  notId :: a -> a
                                                                                
                                                                                instance Stupid a where
                                                                                  notId = id
                                                                                instance Stupid Bool where
                                                                                  notId = not
                                                                                
                                                                                stupidId :: a -> a
                                                                                stupidId = notId

                                                                                Без IncoherentInstances:


                                                                                    • Overlapping instances for Stupid a arising from a use of ‘notId’
                                                                                      Matching instances:
                                                                                        instance Stupid a -- Defined at Main.hs:6:10
                                                                                        instance Stupid Bool -- Defined at Main.hs:9:10
                                                                                      (The choice depends on the instantiation of ‘a’
                                                                                       To pick the first instance above, use IncoherentInstances
                                                                                       when compiling the other instance declarations)
                                                                                    • In the expression: notId
                                                                                      In an equation for ‘stupidId’: stupidId = notId
                                                                                   |
                                                                                13 | stupidId = notId
                                                                                   |            ^^^^^

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


                                                                                Нарушают "регулярность" системы типов языка. Точно не скажу (теперь уже мне матана не хватает).

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

                                                                                  0

                                                                                  Э-э-э, а как вы без IncoherentInstances будете использовать Overlapping instances?

                                                                                    0

                                                                                    Как использовать — да спокойно, делегируя выбор инстанса выше и используя {-# OVERLAPS #-},


                                                                                    например
                                                                                    {-# LANGUAGE FlexibleInstances #-}
                                                                                    
                                                                                    class Stupid a where
                                                                                      notId :: a -> a
                                                                                    
                                                                                    instance Stupid a where
                                                                                      notId = id
                                                                                    
                                                                                    instance {-# OVERLAPS #-} Stupid Bool where
                                                                                      notId = not
                                                                                    
                                                                                    stupidId :: Stupid a => a -> a
                                                                                    stupidId = notId
                                                                                    
                                                                                    main :: IO ()
                                                                                    main = do
                                                                                      print $ stupidId True
                                                                                      print $ stupidId "meh"

                                                                                    Теперь констрейнт с stupidId вы снять не можете, но main допустим и ведёт себя как ожидалось (или не ожидалось, хз).


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

                                                                                      0

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


                                                                                      Окей, вы включили IncoherentInstances и написали stupid :: a -> a ; stupid = notId. У меня не получилось добиться того, чтобы stupid True было False (что разумно — выбор инстанса происходит при компиляции stupidId, а не при её инстанциировании, при её инстанциировании тело может быть вообще недоступно). Получилось ли у вас?

                                                                                        0
                                                                                        Получилось ли у вас?

                                                                                        Неа, я же не проверял как оно в рантайме.


                                                                                        Но если тут нарушается принцип "скомпилировалось -> работает" — это ж ещё хуже, разве нет?

                                                                                          0

                                                                                          Ну почему? Работает же. Выбирается инстанс для a. Если помнить, что тайпчекинг идёт до мономорфизации, то это, наверное, даже выглядит логичным.

                                                                  +1
                                                                  А в восьмой версии языка добавили и switch expressions:

                                                                  static string Display(object o)
                                                                  {
                                                                      return o switch
                                                                      {
                                                                          Point p when p.X == 0 && p.Y == 0 => "origin",
                                                                          Point p                           => $"({p.X}, {p.Y})",
                                                                          _                                 => "unknown"
                                                                      };
                                                                  }
                                                                  


                                                                  Источник
                                                                    +1

                                                                    Можно убрать return и фигурные скобочки


                                                                    static string Display(object o) =>
                                                                        o switch
                                                                        {
                                                                            Point p when p.X == 0 && p.Y == 0 => "origin",
                                                                            Point p                           => $"({p.X}, {p.Y})",
                                                                            _                                 => "unknown"
                                                                        };
                                                                0
                                                                паттен-матчинг по типам?
                                                                А в каком языке (кроме Idris 2) вы это можете делать?

                                                                Go например.
                                                                не так изящно как сигнатурами функций, но вполне рабочий вариант

                                                                  0
                                                                  Такой «паттерн-матчинг» по типам есть много где, где можно узнать тип в рантайме (любой динамический с номинативной типизацией и многие статитические ЯП). switch по метке типа и вперёд. Даже в вашем нике такой ЯП.
                                                                  Перегрузка в сигнатурах функций — тоже неизящный, ad-hoc полиморфизм.
                                                                  –1
                                                                  Стоп, вы про паттен-матчинг по типам? А в каком языке (кроме Idris 2) вы это можете делать?

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

                                                                    0

                                                                    Можете развернуть идею?

                                                                      –1

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

                                                                        +1
                                                                        а размеченное объединение выражается через простое объединение пар

                                                                        Но я всё равно же матчусь по элементам t : T, где T — объединение. Из этого не следует, что я могу матчиться по любым элементам t : *, где * такой, что T : *.

                                                                          –1
                                                                          Но я всё равно же матчусь по элементам t: T, где T — объединение.

                                                                          Или, что одно и то же, по типам Ai, где T = A1 U A2 U… U An. Нельзя отличить эти два кейза, это просто два описания одного и того же.

                                                                  0
                                                                  Например так:

                                                                  trait PatternProducer {
                                                                      fn pattern(&self) -> &'static str;
                                                                  }
                                                                  
                                                                  struct Class {
                                                                      producer_fn: Box<dyn Fn() -> &'static str>
                                                                  }
                                                                  
                                                                  impl Class {
                                                                      pub fn new(pattern: &'static str) -> Self {
                                                                          Class {
                                                                              producer_fn: Box::new(move || pattern)
                                                                          }
                                                                      }
                                                                  }
                                                                  
                                                                  impl PatternProducer for Class {
                                                                      fn pattern(&self) -> &'static str {
                                                                          (self.producer_fn)()
                                                                      }
                                                                  }
                                                                  
                                                                  fn print_pattern(class: &dyn PatternProducer) {
                                                                      match class.pattern() {
                                                                          "a" => println!("Match A"),
                                                                          "b" => println!("Match B"),
                                                                          _ => println!("Unknown pattern")
                                                                      }
                                                                  }
                                                                  
                                                                  fn main() {
                                                                      print_pattern(&Class::new("a"));
                                                                      print_pattern(&Class::new("b"));
                                                                      print_pattern(&Class::new("c"));
                                                                  }


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

                                                                  Если у Вас есть Тьюринг-полный язык, вы можете реализовать свою «типизацию». Обычно это легко сделать в рантайме, в компайл тайме не всегда.
                                                                    0
                                                                    Если говорить, об информации о типе, то ApeCoder говорит правильно. В сях информация о юнионе стирается. Поэтому всегда хранится тип данных в куске памяти, выделенной под юнион. Матчите тип данных, и интерпретируете кусок памяти как тип, который вам нужен. Думаю, не нужно говорить, что хранить тип данных можно самыми незаурядными способами.
                                                                      0
                                                                      1. это не матчинг по типам
                                                                      2. это уже реализовано для вас в трейте Any.
                                                                        0
                                                                        1. Почему нет? Любой тип может возвращать константное значение паттерна.
                                                                        2. Я не против
                                                                          0

                                                                          Матчинг по типам это когда я могу сделать:


                                                                          fn foo<T>() -> String{
                                                                             let ty = T;
                                                                             match ty {
                                                                                i32 -> "This is int",
                                                                                f64 -> "This is double",
                                                                                _ -> "This is something else";
                                                                             }
                                                                          }

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

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

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

                                                                            P.S. Я допускаю, что теоркатщики могут себе придумывать проблемы с параметризированностью типов, но на практике, паттерн матчинг делает код чище. Достаточно пописать на Typescript с его кастрированным паттерн матчингом и Rust/ML/Ваш вариант, и увидеть насколько все проще выглядит на практике. Я уже не говорю про С с union/enum парами.
                                                                              0

                                                                              Речь не про паттерн-матчинг, а матчинг по типам. Это нарушает параметричность, отсюда много всякого плохого следует.


                                                                              Речь про то, что таким образом вы никак не поматчитесь по типам про которые ничего не знаете, да ещё и руками пишете.


                                                                              В сишарпе, к слову, можно. Не считаю это плюсом.

                                                                              –1
                                                                              все равно плохая идея

                                                                              О, Коннор МакБрайд :)


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


                                                                              Я там ниже приводил пример функции, которая тайпчекается Idris 2 (который реализует QTT):


                                                                              notId : {a : Type} -> a -> a
                                                                              notId {a = Nat} _ = 0
                                                                              notId {a = Bool} b = not b
                                                                              notId n = n

                                                                              Если убрать {a : Type} из сигнатуры, то матчиться уже будет нельзя. Причём это всё работает точно так же, как и паттерн-матчинг по обычным значениям в случае, например,


                                                                              doSmth : {n : Nat} -> Vect n a -> ...

                                                                              В этой функции вы можете матчиться на n. Но если вы уберёте {n : Nat}, то всё, нельзя, это рантайм-иррелевантная информация.

                                                                                0
                                                                                О, Коннор МакБрайд :)

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


                                                                                Я там ниже приводил пример функции, которая тайпчекается Idris 2 (который реализует QTT):

                                                                                Я пока на первом сижу, мб к концу года перейду на 2 (но скорее всего — нет, т.к. после книги по идрису начну читать что-то про ремонт :) )


                                                                                В этой функции вы можете матчиться на n. Но если вы уберёте {n: Nat}, то всё, нельзя, это рантайм-иррелевантная информация.

                                                                                Прикольно, да. И не надо туда-сюда конвертеры писать, и параметричность вроде сохранили.

                                                                                  +1
                                                                                  Круто, а я вот по никнеймам не угадываю что за люди передо мной, не так много всех знаю.

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


                                                                                  Я пока на первом сижу, мб к концу года перейду на 2 (но скорее всего — нет, т.к. после книги по идрису начну читать что-то про ремонт :) )

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

                                                                  0

                                                                  А этот инт логически не будет информацией о типе?

                                                                    0

                                                                    Нет, про тип он ничего не знает.

                                                                0
                                                                «row polymorphism» — это records из Elm? Всплывающее в поиске не очень доходчиво…
                                                                  +1

                                                                  Насколько я могу судить по беглому гуглу и примерам (так как не знаю Elm) — похоже, да.

                                                                  0
                                                                  Есть такая тема, как row polymorphism, и это очень клёвая тема. Не знаю, есть ли она в F# (я не знаю F#), но в этом несчастном хаскеле её очень не хватает.
                                                                  Расскажите, пожалуйста, почему raw polymorphism не хватает? Часто встричаю утверждение что его не хватает в Haskell, но почему не объясняется. Разница между ad-hoc и raw в моём текущем представление только в том что в первом нужно генерировать больше бинароного кода, во втором нужно делать виртуальные таблицы методов, и как следствие делать некую рефлексию, при этом не понятно какие возможности по верх добавит второй?
                                                                    0

                                                                    Динамическая диспетчеризация (если я верно понял суть row polymorphism) обычно используются там, где писать ad-hoc реализацию слишком многословно или когда tagged union построить невозможно ввиду особенностей архитектуры проекта. Или там, где система типов не очень богатая (Java, C# и пр.).


                                                                    Не уверен, насколько это применимо к Haskell, сужу по своему опыту кодирования на Rust. Частенько бывает что, отделяя абстракцию от реализации, разработчик получает ситуацию, когда итоговый union тип становится возможно построить лишь в проекте самого верхнего уровня иерархии. И чтобы обеспечить его использование, нужно либо обмазываться полиморфизмом сильнее (что весомо усложняет код), либо использовать динамическую диспетчеризацию.

                                                                      0

                                                                      А как вы с каноничным ad-hoc (в смысле наличия перегрузки функций) напишете функцию, которая работает с любым рекордом, в котором есть поля foo :: Int и bar :: Double?

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

                                                                      Зато это можно гарантировать в ATS [1] [2], который скомпилится в нужный C и всё будет безопасно и быстро [3] :)

                                                                      [1] ats-lang.sourceforge.net/htdocs-old/TUTORIAL/contents/tutorial_all.html#pointers
                                                                      [2] bluishcoder.co.nz/2013/01/25/an-introduction-to-pointers-in-ats.html
                                                                      [3] blog.vmchale.com/article/ats-performance
                                                                      +1
                                                                      Структурную типизацию в C# можно «эмулировать» через интерфейсы.
                                                                        +3
                                                                        Ну это все же номинативная по сути типизация. Потому что два одинаковых интерфейса с разным именем — это разные интерфейсы. Хотя да, гибкость, которая из коробки есть в структурных япах, в сишарпе достигается именно интерфейсами
                                                                          +1
                                                                          Это да… Поэтому я и использовал слово «эмулировать».
                                                                          Хотя, в шарпе всё же есть интересные вещи похожие на структурную типизацию. Например метод Dispose.
                                                                            +2
                                                                            О том и речь. Что любой ЯП будет очень сложно описать бинарными терминами вроде «номинативная типизация»
                                                                              +1
                                                                              Так и мир окружающий не описать бинарными терминами. Добро/зло, чёрное/белое, сильный/слабый… даже — мужчина/женщина :-)
                                                                                0
                                                                                Но вот в описании япов обычно пишут, C# — язык со строгой статической типизацией. Что по-хорошему не совсем так, а иногда и совсем не так
                                                                                  0

                                                                                  вероятно явную динамическую типизацию причисляют к статике.
                                                                                  В шарпе все же надо явно объявить dynamic, а не оно само.

                                                                                    0
                                                                                    Это да. Но вот сильной типизацию в C# назвать сложно.
                                                                                    +3
                                                                                    Да чёрт с ней, с типизацией! Мне вот пытались как-то объяснить как битность процессора «посчитать». Ну вот почему 8080й — это 8-битный процессор, а 8086 — уже 16битный? И там и там есть операции с 16-битными числами, и там и там можно складывать две «половинки» 16-битного регистра и переслать, скажем, нижнюю половинку 4го в вернюю половинку 3го… и даже шина данных у 8088 8-битная! В чём разница-то?

                                                                                    Вначале дискуссия крутилась вокруг внутренней реализации (типа если 6502 увеличивает счётчик команд за два такта, «в два прохода» — то это однознано 8-битный процессор, а если 65816 — этого не требует, то это уже 16-битный процессор), но потом, разумеется, Prescott все карты спутал.

                                                                                    В итоге сошлись на том, что если люди называют какой-то процессор 8-битным — то это 8-битный процессор, а если 64-битным, то 64-битный…

                                                                                    Ну зашибись просто! А это, вроде как, просто о чиселке идёт речь! Его, вроде как, можно бы как-то из наблюдаемой под микроскопом картинки извлечь!

                                                                                    А вот нифига.

                                                                                    А вы хотите, чтобы вам чёткие критерии для классификации языков кто-то дал…
                                                                                      0

                                                                                      А разве битность процессора — это не про адресацию? Т.е. размер указателя на объект из кучи.

                                                                                        +2
                                                                                        Если учесть, что почти все известные науке восьмибитные процессоры используют 16-битный указатель (правда у 8008 он 14 битный)? Нет, вряд ли.

                                                                                        Да даже и у современного x86-64 ведь адрес 48-битный (до Ice Lake, у этих он, прости господи, 57-битный).

                                                                                        Чем, кстати, некоторые нехорошие люди пользуются.

                                                                                        Собственно беда-то вся та же, что и с типизацией: мы хотим одним словом охватить несколько разных объектов (размер РОН'а, размер адресной шины, размер шины данных и так далее).

                                                                                        А в реальном мире эти вещи, внезапно, имеют разный размер. И разные люди, в своих классификациях, выбирают «самым важным» разные вещи…
                                                                              0

                                                                              Если под гибкостью имеется в виду необходимость следовать НЕЯВНЫМ контрактам, то такая гибкость — нафиг не нужна. А нужна возможность извне указать, что один тип совместим с другим типом. Мэдс что то такое для шарпа тут https://youtu.be/WBos6gH-Opk?t=2757 описывает.

                                                                              0

                                                                              Нет, нельзя. Интерфейсы в C# — такие же номинативные типы.

                                                                                –1
                                                                                Давайте для примера C++ возьмём. Это — вот как? С одной стороны такое вот законно:
                                                                                struct A {
                                                                                  int x;
                                                                                };
                                                                                
                                                                                struct B {
                                                                                  int x;
                                                                                };
                                                                                
                                                                                int foo(A a) {
                                                                                  return a.x + 3;
                                                                                }
                                                                                
                                                                                int foo(B b) {
                                                                                  return b.x * 3;
                                                                                }
                                                                                
                                                                                auto result1 = foo(A{3}); // 6
                                                                                auto result2 = foo(B{3}); // 9
                                                                                
                                                                                С другой стороны такое вот:
                                                                                auto result3 = foo(*static_cast<B*>(static_cast<void*>(&A{3}))); // 9
                                                                                
                                                                                законно тоже.

                                                                                И? Это номинативные типы или уже структурные?
                                                                                  0

                                                                                  Не вижу в вашем примере эмуляции структурной типизации через интерфейсы.

                                                                              +1
                                                                              Теоретически, мы можем научить компилятор тайпскрипта генерить нам рантайм проверки для определенных типов, и не тащить метаданные всех типов в рантайм — это было бы даже круче. но команда тайпскрипта следует своей философии не влиять на рантайм, и никогда на это не пойдет.

                                                                              А еще есть compile time рефлексия или процедурные макросы, которые могут преобразовывать AST на этапе компиляции.
                                                                              Это часто используется в языках типа Хаскеля и Раста, туда же можно записать лиспомакросы.


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

                                                                                +1
                                                                                Вот я такой таблицы не нашел
                                                                                  0

                                                                                  Посмотрите typescript-is, хорошая библиотека для рантайм-проверок на основе TS-интерфейсов.

                                                                                  +1
                                                                                  Проблема в том, что есть много случаев, когда мне в коде требуются именно номинативные проверки. Например, у меня есть одинаковые по структуре сущности Customer и Client, и я хочу написать метод, который сохраняет клиента в базу.

                                                                                  Вроде есть некий костыль, который позволяет это делать в TypeScript, некоторые его ещё называют "branded types"
                                                                                  https://medium.com/better-programming/nominal-typescript-eee36e9432d2

                                                                                    +1
                                                                                    Да, я упоминал в статье, что в тайпскрипте есть хитрые костыли, что бы получить номинативное поведение
                                                                                      +1

                                                                                      оу, да, прошу прощения, пропустил этот абзац)

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

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

                                                                                      +1
                                                                                      Есть языки программирования, которые потащат в рантайм метаинформацию этого типа.

                                                                                      Думаю, это называется рефлексией


                                                                                      Получается, что если я описал тип User, и получил список таких юзеров из JSON, мне придётся руками перечислять все свойства, и делать все проверки. Это объективно — говно.
                                                                                      Теоретически, мы можем научить компилятор тайпскрипта генерить нам рантайм проверки для определенных типов, и не тащить метаданные всех типов в рантайм — это было бы даже круче.

                                                                                      Не уверен на 100%, но кажется эта статья может понять куда дальше двигаться, если хочется прокопать это направление
                                                                                      http://blog.wolksoftware.com/decorators-metadata-reflection-in-typescript-from-novice-to-expert-part-4

                                                                                        0
                                                                                        Есть языки программирования, которые потащат в рантайм метаинформацию этого типа.
                                                                                        Думаю, это называется рефлексией
                                                                                        Нет, не называется. Метаинформацию в рантайме в C++ вы получить можете (если RTTI не выключите), а вот рефлексии там нет. Хотя есть Magic Get, позволяющий узнать какие у структуры поля и каких типов и Magic Enum, позволяющий узнать названия элементов Enum.

                                                                                        При этом то же самое, но уже без хитрых трюков — собираются добавить в C++23… и это уже будет называться рефлексией…
                                                                                          0
                                                                                          Нет, не называется. Метаинформацию в рантайме в C++ вы получить можете (если RTTI не выключите), а вот рефлексии там нет.

                                                                                          Хм, интересно… А что ещё из обязательного включается в понятие рефлексии, что получение метаинформации о типах через RTTI нельзя ей назвать?

                                                                                            0
                                                                                            Тут как и с типами. Единого, твёрдо устоявшегося определения нет. Но можно глянуть что Wikipedia пишет: Рефлексия может использоваться для наблюдения и изменения программы во время выполнения. Рефлексивный компонент программы может наблюдать за выполнением определённого участка кода и изменять себя для достижения желаемой цели. Модификация выполняется во время выполнения программы путём динамического изменения кода.

                                                                                            Вот наблюдать за чем-то можно (но очень ограниченно), а вот «модифицировать себя» — тут напряг. Даже в компайл-тайм нельзя, не говоря про рантайм. Вся поддержка рефлексии, фактически, нужна в C++98, чтобы dynamic_cast работал. Это, как бы сказать, очень ограниченный вариант.
                                                                                        0

                                                                                        Насколько я понял из https://en.wikipedia.org/wiki/Type_theory нет какой-то единственной и универсальной системы типов, а соответственно и сгруппированных по этой классификации языков программирования

                                                                                          +2

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


                                                                                          List<String>
                                                                                          List<Int>

                                                                                          друг от друга (привет Java).
                                                                                          Опять же, что касается интерфейсов, то помимо традиционной схемы с наследованием есть языки, где можно реализовывать интерфейсы для произвольных типов, в том числе чужих. Привет Haskell type class.
                                                                                          Но некоторым языкам и такого метапрограммирования мало, они вводят еще и макросы, которые бывают как гигиеническими, так и нет.
                                                                                          Получается, что в такой вот общей классификации было бы ну дохрена различных пунктов.

                                                                                            0

                                                                                            Интересно почему метод падал на Customer и Client, если они структурно одинаковые?


                                                                                            Ну а так заценил преимущество структурной типизации работая с graphql.


                                                                                            Долго пытался протаскивать типы из кверей, а потом понял, что если я в UI компоненте отображаю id, name и value — то надо просто написать что этому UI компоненту нужна сущность с id, name и value — а уже тайпскрипт проверит, что туда спускается из квери соответствующий объект.

                                                                                              +1
                                                                                              Интересно почему метод падал на Customer и Client, если они структурно одинаковые?


                                                                                              Имел ввиду, что метод как раз не упал — произошёл более плохой вариант, когда у нас сущность не в ту таблицу записалась
                                                                                                0

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

                                                                                                  0
                                                                                                  Тоже от ЯП зависит. В номинативном фарше можно наоборот
                                                                                              0

                                                                                              Номинальная и структурная типизация могут уживаться в одном языке. Например во flow вы можете задавать их явным образом https://flow.org/en/docs/lang/nominal-structural/ .


                                                                                              В typescript преимущественно используется структурная типизация, однако есть несколько специальных случаев, когда типы начинают вести себя как номинальные. Это выглядит скорее как изъян в дизайне, чем осмысленное решение. История давняя, если интересно, следите за https://github.com/Microsoft/Typescript/issues/202 .

                                                                                                +3

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


                                                                                                Система типов это не священная корова, а чисто утилитарный инструмент для проверки программы на наличие логических ошибок. Как существует великое множество различных логик со своими законами, так и дизайн каждой из таких систем отражает видение авторов конкретного языка программирования и является следствием каких-то решений и компромиссов. Закономеро, что в разных языках они отличаются. В пределе, выразительные возможности упираются в теорему Райса https://en.m.wikipedia.org/wiki/Rice%27s_theorem. Поэтому не существует какой-то единственно правильной или однозначно лучшей системы. Расслабьтесь и пользуйтесь тем, что даёт язык, либо попробуйте придумать свой.

                                                                                                  0
                                                                                                  Ну как сказать, полярные штуки хороши тем, что поднимают дискуссию. А в этой статье я не вижу ничего полярного, и даже не ищу какой-то совершенной модели, просто пытаюсь разобраться в терминах
                                                                                                  0
                                                                                                  Вот подробный материал
                                                                                                  habr.com/ru/post/477448
                                                                                                    0
                                                                                                    <code=typescript>function test(a: A) {}
                                                                                                    в
                                                                                                    <code=typescript>function test(a: NoExtraProperties<A>) {} (тип NoExtraProperties можно нагуглить или самому написать)
                                                                                                      0
                                                                                                      Причем теоретически, я могу заставить тайпскрипт валидировать полное соответствие — как тогда мне называть его модель типизации? Понятия не имею.
                                                                                                    • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                        +3
                                                                                                        Ну кому как. Мне так наоборот хочется разделить все числа посильнее, чтобы метры с килограммами сложить было нельзя. Или чтобы id пользователя и id товара были разные типы, несмотря на то что оба номинально int (или string, неважно)

                                                                                                        «Ритуальная шелуха» в современных статически типизированных языках существет только в интерфейсах методов и объектов. И в динамических языках она также существует, только занимает гораздо больше места (в виде проверок на правильность типов аргументов и/или тестов вида «что если передать объект неправильного типа»). В «проектах» на 20 строк кода такого, конечно, может и не быть.

                                                                                                        Вот недавно была задачка. В проекте использовались 2d координаты (x, y) в одном домене. Потом понадобилось соединить их с другим доменом в котором использовались 3d координаты (x, y, z) причем «y» там означала высота, и правильная конвертация была такая: (x, y) => (x, 0, y) т.е. y переименовывался в z

                                                                                                        Я могу только представить какая это была бы боль в структурно типизированном языке или вообще языке без типов. Но к счастью язык был статически типизируемым и все что понадобилось — это пара предоставляемых IDE рефакторов плюс обновить несколько математических функций.
                                                                                                        • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                            +2

                                                                                                            метры вместо килограмм — редкая. Секунды вместо миллисекунд — уже почаще.


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


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


                                                                                                            Ну и самое главное преимущества из-за которого я никогда не смогу продуктивно работать на динамическом языке — это то что с нормальным инструментарием можно всегда точно сказать что какой-то объект/функция не используется, и точно найти все использования. Это очень сильно играет на чистоту кода — любое "старье" можно быстро отрефакторить или выпилить с минимальными шансами что-то сломать.

                                                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                +2

                                                                                                                Что такое "вполне типизированный"? Как F# в котором есть поддержка единиц измерения? Или как C?

                                                                                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                    +3
                                                                                                                    Если что — у си слабая типизация. Он почти как жабаскрипт, только c
                                                                                                                      0
                                                                                                                      Это как раз подтверждает, что реально нужно более сильное разделение между типами, чем просто операторы для строк и чисел. В Си оператора сложения строк вообще нет, но это не влечёт отсутствие (или почти отсутствие) ошибок, которые можно было бы легко решить типами.
                                                                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                          +1
                                                                                                                          В чём проблема-то вообще?
                                                                                                                          f(t, v0, a) = v0 * t + a * t^2 / 2
                                                                                                                          f(1u"s", 1u"m/s", 1u"m/s^2") # выдаёт 1.5 m
                                                                                                                          f(1u"s", 1u"m/s", 1u"m/s") # выдаёт ошибку
                                                                                                                          f(1u"s", 1u"kg/s", 1u"kg/s^2") # 1.5 kg

                                                                                                                          если последний пример не нравится (хотя он имеет физический смысл), можно ограничить чтобы обязательно получалось расстояние:
                                                                                                                          f(t, v0, a) = v0 * t + a * t^2 / 2 |> u"m"

                                                                                                                          Тогда с килограммами пример тоже ошибку даст.

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

                                                                                                                            Если функция s дана в виде


                                                                                                                            s : (v : Speed) -> (a : Acc) -> (t : Secs) -> Meters

                                                                                                                            где


                                                                                                                            Speed : Type
                                                                                                                            Speed = Meters // Secs
                                                                                                                            
                                                                                                                            Acc : Type
                                                                                                                            Acc = Speed // Secs

                                                                                                                            то я набросаю типы вроде


                                                                                                                            sStartsAt0 : s v a 0 = 0
                                                                                                                            
                                                                                                                            sTimeLinear : (t1, t2 : Secs) -> s v 0 t1 + s v 0 t2 = s v 0 (t1 + t2)
                                                                                                                            
                                                                                                                            sSpeedLinear : (v1, v2 : Speed) -> s v1 0 t + s v2 0 t = s (v1 + v2) 0 t

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

                                                                                                                      0

                                                                                                                      Да. Поэтому и мой комментарий:


                                                                                                                      Мне так наоборот хочется разделить все числа посильнее, чтобы метры с килограммами сложить было нельзя.
                                                                                                                    • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                        +2

                                                                                                                        Упадет. Но по крайней мере в базе данных не окажется лицензионного соглашения в поле "широта" :)


                                                                                                                        Вообще-то все современные языки умеют приводить типы. И решать задачу "как получить int из json если там вдруг строка" должен автор библиотеки json сериализации (т.е. в 99% случаев — не пользователь библиотеки). Т.е. ситуация оказывается строго лучше — мы все также можем распарсить число пришедшее в json в виде строки, но только у нас теперь есть еще и дополнительные гарантии от компилятора.

                                                                                                                          0

                                                                                                                          Что-то не припомню, чтобы апологеты типов заявляли о ненужности тестов (а вот наоборот – бывало).

                                                                                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                              +2

                                                                                                                              Он написал о ненужности юнит тестов, а не тестов вообще. Юнит тесты многие не любят

                                                                                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                  +2
                                                                                                                                  "многие не любят" — это что-то странное

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

                                                                                                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                      +2

                                                                                                                                      Задавать окружение какого-то класса путём поднятия всей системы — не юнит-тест

                                                                                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                          +1

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

                                                                                                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                              +1

                                                                                                                                              веб-серверы — это про nginx или про приложение на каком-нибудь фреймворке?

                                                                                                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                  +1

                                                                                                                                                  Нормально я их тестирую юнит тестами только для одного юнита с помощью моков. И общение с операционной системой типа файлов или часов мокаю. На JS/TS или PHP

                                                                                                                                                0
                                                                                                                                                общение с операционной системой уже не замокать.

                                                                                                                                                О, а я читал в конце апреля статью, в которой рассказывалось про то, как мокали общение с OS

                                                                                                                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                    +2

                                                                                                                                                    Странная у вас оценка. Инфраструктура для моков создаётся один раз и переиспользуется многократно.

                                                                                                                                                    • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                        0
                                                                                                                                                        Если мок инициализируется перед каждым запуском теста, то мы заранее знаем, какими должны быть его вызовы и какими ответы в этом тесте. В чём тут сложность?
                                                                                                                                                  0

                                                                                                                                                  Интересно, как по-вашему живут все те компании, которые предпочитают типы тестам и при этом пишут что-то сложнее сортировок.


                                                                                                                                                  но 0xd34df00d предпочёл продолжать оставаться на уровне программирования примитивов. а это говорит "мне неинтересно"

                                                                                                                                                  На самом деле на уровне программирования вещей для программирования примитивов, а там есть, где развернуться.


                                                                                                                                                  Какой-нибудь оптимизатор внутри компилятора вы предпочтёте тестировать или таки согласитесь на типы?

                                                                                                                                                    0
                                                                                                                                                    какой оптимизатор? сказали евалом порешают! евалом
                                                                                                                                                    • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                        +1
                                                                                                                                                        Вы мне рассказывали что якобы работаете в такой, но когда я спросил что делаете конкретно: спрятались за NDA
                                                                                                                                                        дескать работодатель из америки тут на хабре увидит и уволит

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


                                                                                                                                                        Впрочем, что, например, последнее место было хедж-фондом с торгами на бирже, я не особо скрываю.

                                                                                                                                          0
                                                                                                                                          берём юнит, который содержит код, ходящий в http или в Database.
                                                                                                                                          его тест — юниттест или не юниттест?

                                                                                                                                          Юнит тест это размытое понятие. Что конкретно имел ввиду 0xd34df00d под ним — надо спросить у него.


                                                                                                                                          "многие не любят" — это что-то странное.

                                                                                                                                          Почему. Многие не любят острую пищу. Чем это странное понятие? В реальности такое считается.


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

                                                                                                                                            +1
                                                                                                                                            Юнит тест это размытое понятие. Что конкретно имел ввиду 0xd34df00d под ним — надо спросить у него.
                                                                                                                                            Тест, не общающийся с «внешним миром». Неважно — замоканы другие компоненты или просто задача не требует общения.
                                                                                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                0

                                                                                                                                                Не важно где они там на практике встречаются. Важно какие тесты можно назвать юнит-тестами, а какие нельзя. Потому что если называть одним и тем же словом всё подряд — вас перестанут понимать.

                                                                                                                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                    +3
                                                                                                                                                    Интересно, а зачем спорить, когда ты настолько некомпетентный?
                                                                                                                                                      +2
                                                                                                                                                      Вы просто неправильно определили профессию rsync. Он — невероятно компетентен в своей профессии. Просто это — не написание программ и не решение каких-то задач, а объяснение всем остальным, что они «ничего не понимают в колбасных обрезках».

                                                                                                                                                      Для этого — что-то там «понимать» или там «определять» — не только не нужно, но даже вредно. Ибо мешает набрасывать лапшу на уши.
                                                                                                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                          0
                                                                                                                                                          что вы и продемонстрировали в своей недавней статье про работу в яндексе?
                                                                                                                                                      0

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

                                                                                                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                          +1

                                                                                                                                                          Юнит тест — да, такому коду не написать.

                                                                                                                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                            +1

                                                                                                                                                            В юнит-тесте я хочу тестировать свой модуль, а не внешнюю систему или соседний модуль. Если тест падает, то ошибку буду искать в своём модуле.

                                                                                                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                                0

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

                                                                                                                                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                                    +1

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


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


                                                                                                                                                                    UPD:


                                                                                                                                                                    Ещё раз: юниттест — это тест, тестирующий поведение кода внутри юнита.

                                                                                                                                                                    Правильно, но чужая система не ваш юнит.

                                                                                                                                                                      +1

                                                                                                                                                                      Да вам же и нужно:


                                                                                                                                                                      тесты называются юниттестами если они тестят код внутри юнита.

                                                                                                                                                                      Если тесты тестят внешний мир, то какие-же они юнит-тесты?


                                                                                                                                                                      А избавляться от внешнего мира нужно по двум основным причинам:


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

                                                                                                                                                                      А за интеграцию юнитов друг с другом или юнита с внешним миром, отвечают, как ни странно, интеграционные тесты.

                                                                                                                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                  0

                                                                                                                                                  Смотря какой тест.


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


                                                                                                                                                  Юнит-тест ли это или нет?

                                                                                                                                          0
                                                                                                                                          А еще было прикольно, как в случае планёр Гимли, когда в пассажирский самолет вместо литров фунты залили, и на полпути у них двигатели встали.
                                                                                                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                              0
                                                                                                                                              все вышеприведённые примеры (спутник упал, самолёт перевернуло, самолёт чуть ни упал, итп) приведены в аргумент «хорошо писать на языках с типами», но все эти примеры написаны именно на языках с типами
                                                                                                                                              Ну если вы знаете примеры самолётов с софтом на нетипизированных языках и можете аргументированно показать что им разницы операторов между целыми числами и строками хватило — то, пожалуйста, можете статью написать.

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

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

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


                                                                                                                                                Давайте все программы вот так писать:

                                                                                                                                                >The fly-by-wire flight software for the Saab Gripen (a lightweight
                                                                                                                                                >fighter) went a step further. It disallowed both subroutine calls and
                                                                                                                                                >backward branches, except for the one at the bottom of the main loop.
                                                                                                                                                >Control flow went forward only. Sometimes one piece of code had to leave
                                                                                                                                                >a note for a later piece telling it what to do, but this worked out well
                                                                                                                                                >for testing: all data was allocated statically, and monitoring those
                                                                                                                                                >variables gave a clear picture of most everything the software was doing.
                                                                                                                                                >The software did only the bare essentials, and of course, they were
                                                                                                                                                >serious about thorough ground testing.
                                                                                                                                                >
                                                                                                                                                >No bug has ever been found in the «released for flight» versions of that
                                                                                                                                                >code.
                                                                                                                                                  0
                                                                                                                                                  Давайте все программы вот так писать:
                                                                                                                                                  Дорого так писать очень. Но вообще — можно и так.

                                                                                                                                                  Дорого так писать очень. Но вообще — можно и так.

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

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

                                                                                                                                                  И да — я ни разу не макетолог и не продажник. Если мы порождаем, я извиняюсь, дерьмо — то я так и говорю: мы порождаем дерьмо. А не «конфету с альтернативным вкусом».

                                                                                                                                                  И дважды два для меня — четыре. Независимо от того, продаём мы или покупаем.

                                                                                                                                                  Ну вот так.

                                                                                                                                                  А если вам хочется изгнать математику из программирования, чтобы продавать «конфеты с альтернативным вкусом»… да ради бога: пока мне это не приходится расхлёбывать — продавайте кому угодно и что угодно… только без меня.
                                                                                                                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                      0
                                                                                                                                                      как раз динамически типизируемый язык позволяет делать меньше ошибок. Именно потому что код алгоритма очищен от ритуальных ненужностей и программист может видеть/понимать больше/лучше
                                                                                                                                                      Так пример самолёта или хотя бы автомобиля с управляющим ПО на таком языке — будет али нет?
                                                                                                                                                      • НЛО прилетело и опубликовало эту надпись здесь