1. Swift / Говнокод #23907

    +1

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    32. 32
    33. 33
    34. 34
    35. 35
    36. 36
    37. 37
    38. 38
    39. 39
    40. 40
    41. 41
    42. 42
    43. 43
    44. 44
    45. 45
    46. 46
    47. 47
    48. 48
    protocol Multi {
        associatedtype T
        associatedtype U
    
        func printSelf()
    }
    
    extension Multi where T == Int, U == Float {
        func printSelf() {
            print("Int & Float!")
        }
    }
    
    extension Multi where T == String, U == Int {
        func printSelf() {
            print("String & Int!")
        }
    }
    
    extension Multi {
        func printSelf() {
            print("Unknown")
        }
    }
    
    class MultiImplementationIntFloat: Multi {
        typealias T = Int
        typealias U = Float
    }
    
    class MultiImplementationStringInt: Multi {
        typealias T = String
        typealias U = Int
    }
    
    class MultiImplementationInvalid: Multi {
        typealias T = Float
        typealias U = String
    }
    
    let m1 = MultiImplementationIntFloat()
    m1.printSelf()
    
    let m2 = MultiImplementationStringInt()
    m2.printSelf()
    
    let m3 = MultiImplementationInvalid()
    m3.printSelf()

    Multimethods в Swift с проверкой в compile-time

    Запостил: Desktop, 10 Марта 2018

    Комментарии (31) RSS

    • Типа ты сделал экстеншен методы разные, в зависимости от того, каким типом параметризован интерфпротокол?

      Это не совсем честно, потому что завист от типа m, а не от переданных в printSelf аргументов, с таким же успехом ты мог printSelf в каждом классе переопределить:)
      Ответить
      • Разница огромна. Чтобы переопределить в каждом классе, нужно иметь к нему доступ. А здесь можно где угодно сделать примесь. В самом коде не писал, но вообще имелось ввиду:

        class blahblah { }
        и где-то в недрах клиентского кода
        extension blahblah: Multi {...}

        И получаешь миксин
        Ответить
        • Рубист бы навесил метод на класс, и даже не задумался бы

          Я имел ввиду разницу в точки зрения диспатча: всё таки тип m2 известен в момент компеляции, и потому свифт может подобрать нужный метод статически. Котлин тоже так может, например:)
          Полезность extensionов я под сомнение не ставлю, это супер-крутая фича для method discovery (которая, кстати, была в ObjC [категории называлась вродеъ задолго до Kotlin, и наверное до C#)
          Ответить
          • Идея кмк одинаковая.

            Категории ОбжСи сосут у Свифта в данном случае: там нет default implementation
            Ответить
            • Идея была про вызов разных методов в рантайме в зависимости от параметров.
              Представь себе такой псевдокод
              fun doAll(cat: Cat){}
              fun doAll(dog: Dog){}
              ///
              let data:Mammal = getMammal();
              //data может быть cat, а может и dog
              doAll(data); //вызовет разные методы в зависимости от типа в рантайме

              В твоем случае это не так, потому что тип data известен в момент компеляции.

              А вот с перегрузкой методов или с visitor это бы сработало.

              >>Категории ОбжСи сосут у Свифта в данном случае: там нет default implementation
              Ты про print("Unknown")?

              Удобно, да. Я почти ничего не знаю про Swift:( Во время моего близкого знакомства с Apple везде был Objc, кажется только ARC завезли.
              Ответить
              • Моя идея была в

                "Сами generic functions тоже не похожи. В CL они не привязываются к одному конкретному типу. Например, можно определить функцию (defgeneric intersects (a b)) и написать методы для случаев (defmethod intersects ((a rectangle) (b circle))), (defmethod intersects ((a circle) (b circle))), (defmethod intersects ((a interval) (b point)))…
                В рантайме будет выбран наиболее подходящий эффективный метод. Т.е. это настоящие мультиметоды." (c) roman с той разницей, что выбор будет в компайлтайме.

                > В твоем случае это не так, потому что тип data известен в момент компеляции.

                У тебя он тоже известен, это ж Mammal.

                > А вот с перегрузкой методов или с visitor это бы сработало.
                - превед ООПед

                > Ты про print("Unknown")?
                - не. Я про то, что можно:
                1) определить протокол
                2) заэкстендить его, дав некоторым методам default implementation
                3) потом заэкстендить какой-то класс этим протоколом, voila!, класс умеет в default implementation методов протокола

                В ObjC так нельзя (ну или я чего-то не знаю).

                Ещё таким образом можно сэмулировать @optional из обж сей. Экстендим метод пустым телом, кому реально надо, реализует сам.
                Ответить
                • >>У тебя он тоже известен, это ж Mammal.
                  И что? Этой информации не достаточно чтобы выбрать метод в комплайт тайме.
                  А в твоем случае достаточно. Смотри:

                  Компилятор в твоем примере на строке 42 (m1.printSelf()) думает: "а что за метод такой -- printSelf? Ах, у нас же тип m1 это MultiImplementationIntFloat, значит и метод понятно какой". Экстеншены в котлине резолвятся статически, в момент компиляции, думаю что и в Свифте -- тоже.

                  А в моем примере он НЕ знает типа data и вынужден отложить решение о выборе метода до рантайма, как если бы это был виртуальный метод.

                  В том-то и разница.

                  >>- превед ООПед
                  Ну в мейнстрим ЯПах с ООП обычно только так и бывает)

                  >> класс умеет в default implementation методов протокола
                  Но весь код, юзающий класс, придется перекомпилировать, да?

                  >> сэмулировать @optional из обж сей.
                  А у вас нету optinal? А спросить respondsToSelector можно?
                  Ответить
                  • > Но весь код, юзающий класс, придется перекомпилировать, да?
                    - да.

                    > А у вас нету optinal? А спросить respondsToSelector можно?
                    - у нас это в свифте? Только в @objc протоколах. В обычных свифтовых нету. Спросить respondsToSelector можно у классов, которые известны рантайму ObjC (т.е. потомки NSObject)
                    Ответить
          • Вообще, кстати, неоспоримость полезности extension'ов под вопросом. Примеси делать или в рамках одного файла разбивать на функциональные элементы да, пихать всюду и везде - яйца за такое отрывать
            Ответить
            • Extensions позволяют легче найти нужный API.
              Допустим, у меня есть user. Я хочу его удалить. Интуитивно я ищу метод delete(), но конечо его нет в классе User (потому что он ничего про удаление себя не знает).
              Тогда я говорю user.[cntrl space] и IDE находит мне все его extension методы (по сути те, где он является первым аргументом). Так я нахожу свой метод. Но так делает Intellij для Kotlin, и кажется R# для C#. Делает-ли так XCode для Swift я не знаю (думаю что может быть и нет), но может AppCode делает.

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

              Проблему можно решить документированием API, но где (кроме публичных проектов) такое бывает? В проприетарном коде, которым пользуются 20 человек в одной конторе, почти никого такого нет:(((

              Вот если бы IDE умели "найти все публичные методы, доступные в этом модуле/пакете, где определённого класса инстанс является одним из параметров" то может быть было бы не плохо и без exntesions, но всё равно явный вызов метода выглядит приятнее.
              Ответить
              • > найти по параметрам
                Емнип, в squeack (одна из реализаций smalltalk) была такая фишка.
                Ответить
              • > Делает-ли так XCode для Swift я не знаю (думаю что может быть и нет)
                - проверил, для классов умеет, для протоколов нет. XCode это блокнот, они рефакторинг для свифта только к 4 версии языка смогил добавить.

                > Вот куда его класть -- вопрос.
                - ну на крайняк в TypeNameExtensions.swift.

                > Интуитивно я ищу метод delete(), но конечо его нет в классе User (потому что он ничего про удаление себя не знает).
                Тогда я говорю user.[cntrl space] и IDE находит мне все его extension методы
                > Проблему можно решить документированием API
                - когда что-то ищется интуитивно и не находится, то kill hire repeat
                Ответить
                • >> XCode это блокнот
                  У меня тоже было такое ощущение, например его парсер не умеет восстанавливаться: забыл ] в одном месте и всё дерево пошло по песде. Я помню что думал пересесть на AppCode, но побоялся что сториборды не будут там работать:)

                  >>- ну на крайняк в TypeNameExtensions.swift.
                  Немножечко "god objfile"

                  >>то kill hire repeat
                  У нас в большинстве проектов оче хуёво с API:((
                  Ответить
                  • >Я помню что думал пересесть на AppCode
                    они уже пару лет точно забили на UI, потому что яббл в каждом релизе что-нибудь меняет. Я пишу код в appcode, ui - в xcode. Коллеги жалюутся на то, что в xcode постоянно падает парсер и он превращается в блокнот. Appcode как-то стабильнее, но тоже может не увидеть либу или какой-нибудь экстеншн

                    И да, сториборды - говно. Лучше делать отдельные xib
                    Ответить
                    • >>сториборды - говно
                      почему? Мне нравилось визуально наблюдать перемещение экранов, и сигвеи тоже нравились
                      Ответить
                      • Особенно классно в сторибордах фиксить конфликты в vcs
                        Ответить
                        • Их классно фиксить в любой автогенеренной хуйне.
                          Нужна пессимистичная блокировка, как была в source safe:)
                          Ответить
                          • Не, сториборды и все прочие xcproj имени эппла это такое особое извращение.

                            Но вообще да, много маленьких сторибордов вместо одной большой + lock, если возможно
                            Ответить
                    • Забавно, что Visual Studio for Mac умеет хоть как-то работать с Interface Builder.
                      Ответить
    • Совершенно непонятно, зачем это надо, если инстанс руками создавать надо. Ну а в компайл-тайме "мультиметоды" и в крестах легко сделать.
      // https://ideone.com/GZowyg
      #include <iostream>
      #include <utility>
      
      template <class U, class V>
      struct intersect_impl;
      
      template <class U, class V>
      bool intersect(const U& u, const V& v) {
          return intersect_impl<U, V>::apply(u, v);
      }
      
      template<>
      struct intersect_impl<std::pair<int, int>, int> {
          static bool apply(const std::pair<int, int>& interval, int value) {
              return interval.first <= value && value <= interval.second;
          }
      };
      
      template<>
      struct intersect_impl<std::pair<int, int>, std::pair<int, int>> {
          static bool apply(const std::pair<int, int>& u, const std::pair<int, int>& v) {
              return intersect(u, v.first) || intersect(u, v.second);
          }
      };
      
      int main() {
          std::cout << "[0, 2] ∩ 1 ? " << intersect(std::make_pair(0, 2), 1) << "\n";
          std::cout << "[0, 2] ∩ 1 [3, 4] ? " << intersect(std::make_pair(0, 2), std::make_pair(3, 4)) << "\n";
          std::cout << "[0, 2] ∩ 1 [1, 4] ? " << intersect(std::make_pair(0, 2), std::make_pair(1, 4)) << "\n";
          return 0;
      }
      Вся суть настоящих мультиметодов в том, что можно сложить объекты разных типов в гетерогенный контейнер и работать с ними универсальным образом.
      Ответить
      • Инстанс чего?

        > Вся суть настоящих мультиметодов в том, что можно сложить объекты разных типов в гетерогенный контейнер и работать с ними универсальным образом.
        - зачем нужна гетерогенная динамическая метушня, если в общем случае проблем с ней больше? Вон Пи сокрушался по поводу того, что эксепшон вылетит в C#, если нет нужной перегрузки. Кстати, как CL будет вести себя в таком случае?
        Ответить
        • > Инстанс чего?
          >> let m1 = MultiImplementationIntFloat()

          > зачем нужна гетерогенная динамическая метушня

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

          > Кстати, как CL будет вести себя в таком случае?
          Если совсем нет реализации, кинет исключение "кондишен". Тех конфликтов, которые пи нашёл в c#, там не будет, там по-другому "перегрузки" разрешаются. http://govnokod.ru/23894#comment407460
          Ответить
          • А, так это пример просто, никто ж не мешает законформить уже существующий тип к протоколу:
            extension Int: Multi { ... }
            и поехали

            > Ну вот есть у тебя все объекты какой-нибудь игры, ты хочешь положить их в дерево, чтобы быстро считать столкновения.
            - composition же. У объектов должны быть geometric bodies, по ним и считать. Geometric bodies по идее должны обладать одинаковыми геометрическими свойствами, так что гетерогенность вроде и ни к чему здесь. А, если хочется динамики, не взять ли duck typing с чем-то типа forwardInvocation (это из смолтолка пришло вроде)?
            Ответить
            • > У объектов должны быть geometric bodies, по ним и считать

              Это что-то меняет? Ну хранишь ты геометрию вместо реальных объектов. Всё равно потом надо найти, каким объектам эта геометрия принадлежит, и вызвать соответствующую типам функцию.
              Ответить
              • Если я храню только геометрию, то зачем мне гетерогенный список?) Я теряю причинно-следственную связь беседы.

                Но в ObjC даже проще сделать то, о чем ты говоришь
                Ответить
                • > Если я храню только геометрию, то зачем мне гетерогенный список?) Я теряю причинно-следственную связь беседы.

                  Ну так можно утверждать, что Tree<GameObject> никакой не гетерогенный контейнер. В нём же только GameObject-ы лежать могут.

                  Допустим, у тебя какой-нибудь "однородный" SpaceTree<Polygon>. Дерево помогло тебе быстро посчитать, что полигон p1 теперь пересекается с полигоном p2. Что дальше-то будем делать? Теперь надо понять, чем именно являются полигоны p1 и p2 и вызвать соответствующую функцию, ибо корабли, к примеру, взрываются и аннигилируют, а астероиды распадаются на несколько мелких астероидов. Вот тут ты и будешь пилить наколенный аналог мультиметодов.
                  Ответить
                  • Обычно столкновения считает физический движок, который является отдельной либой, и в принципе не знает ни про какие корабли и астероиды, т.к. работает он с какими-то своими сущностями, и просто создаёт ивенты, которые уже обрабатывает другой слой.
                    Внутри него есть некий отдалённый аналог "мультиметодов", т.к. нужно обрабатывать столкновения soft и rigid bodies, например, но количество типов сущностей известно и невелико, а требования к перфомансу существенны, так что CLOS и там нафиг не впёрся.
                    Ответить
                    • Ты правой дрочишь, или левой? Только честно. Это важно.
                      Ответить
                      • Вы хотите подарить ему вот такой агрегат и волнуетесь по поводу асимметрии?
                        http://i5.otzovik.com/2017/03/03/4590066/img/6653156.jpeg
                        Ответить
                    • > Обычно столкновения считает физический движок
                      > CLOS и там нафиг не впёрся

                      Это всё и так понятно, можно десятилетиями песать опупенные сайты игоры без всякого там «CLOS».

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

    Добавить комментарий