Видеоблогер Конор Хекстра использовал разные языки программирования, чтобы решить одну и ту же задачу. Попутно выяснилось, что у Фортрана полно поклонников.

На YouTube-канале code_report, «посвященном соревновательному программированию», есть интересная работа Конора Хекстры, старшего инженера NVIDIA, работающего над пакетом RAPIDS для конвейеров обработки данных и аналитики. Хекстра увлекается просмотром решений с конкурсов по программированию (LeetCode, HackerRank, Topcoder и Codeforces), а также создаёт видеоролики на близкие к программированию темы, такие как структуры данных и алгоритмы.

У Фортрана оказалось много поклонников

Чтобы исследовать подходы к решению одной задачи на 16 разных языках программирования, Хекстра выбрал относительно простое задание с конкурса программирования LeetCode: определите наименьшее и наибольшее число из заданного списка, а затем найдите их наибольший общий делитель.

Языки, выбранные для решения:

  • C++

  • Rust

  • D

  • Clojure

  • Ruby

  • Elixir

  • Raku [Perl 6]

  • Haskell

  • Racket

  • Julia

  • Python

  • APL

  • J

  • BQN

  • Pharo Smalltalk

  • Fortran

(В видео поясняется, что последние два языка были добавлены по просьбам зрителей. «Я сделал опрос в Твиттере, и Fortran получил целых 27 лайков!», — объясняет Хекстра. Так что пришлось его тоже включить).

Кардинальные различия

Хотя исходная задача была простой, подходы к её решению в разных языках сильно отличаются. Особенно при переходе на язык массивов. Там подход будет принципиально отличаться от того же Python или Ruby.

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

Но всё стало выглядеть абсолютно по-другому, когда Хекстра добрался до функционального языка Haskell. Его функция liftM2 отображает значения минимума и максимума для входных данных функции gcd — и все это в одной строке.

И самое главное, поскольку Haskell использовал стиль программирования “Tacit” или point-free (стиль программирования без промежуточных переменных, предполагает использование конвейерных функций и комбинаторов), который не указывает аргументы в определениях функций, решение Haskell даже не нуждалось в упоминании массива чисел.

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

Хекстра жаловался, что в Rust и в C++ слишком много «формальностей». Обе функции, и min, и max, требуют использования iter() для прохождения итераций по каждому значению в списке чисел, и unwrap() для извлечения значения из более сложного модуля Result, который включает информацию об обработке ошибок.

И, как заметил Хекстра: «чтобы получить доступ к функции gcd приходится преодолеть кучу лишнего в пространстве имён num::integer:: . Но в остальном это очень хорошее решение».

Так как Хекстра является поклонником языков программирования массивов, в его исследовании нашлись и более существенные различия. Решение в APL (справа от розовой стрелки) состоит всего из пяти символов. (Первые два символа находят максимальное значение, последние два символа находят минимальное значение, а зеленая буква v в середине находит их наибольший общий делитель.)

Похожая структура есть и в языке программирования J (ещё один язык программирования массивов, но J является усовершенствованной версией APL, в которой используются диаграммы ASCII, а не символы Unicode»). Функция нахождения максимума представлена здесь ​​​​символом “>”, а функция поиска минимума — “<”.

Чем дальше, тем всё более экзотическими становились решения. BQN из семейства  APL (согласно APL Wiki) всё ещё является языком, управляемым массивами, но он использует свой уникальный набор символов. Поскольку в нём нет встроенной функции для нахождения наибольшего общего делителя, эта функция должна быть определена в отдельной строке кода.

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

Соревнование со зрителями

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

“Некоторые интересовались решением для Cobol, так что я решил попробовать свои силы: https://t.co/Pi7OSCdeIl Всего-то 91 строка…

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

Решение на языке Julia был похоже на решение для Python, только с оператором «splat» (в данном случае троеточие), означающим, что будет проверено более одного значения:

Но зрители предложили более простой синтаксис. В Julia есть две встроенные функции поиска минимума и максимума, которые не требуют многоточия. Также есть одна функция, которая возвращает оба значения “extrema”. А затем решение стало еще проще, когда Хекстра взял оператор склеивания функций для создания point-free решения (с функцией сбора, преобразующей два значения в формат списка, который можно передать в gcd).

«На мой взгляд, это самое красивое решение из всех, — добавил Хекстра — отчасти потому, что оно демонстрирует элегантность point-free решений.»

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

По иронии судьбы, решение Pharo Smalltalk оказалось похожим на решение Raku (со своей собственной функцией gcd:, которая появлялась как «инфиксный» оператор между двумя функциями, возвращающими минимум и максимум).

Но зрители отметили, что у Raku также есть специальная функция minmax, которая возвращает оба значения одновременно, что приводит к ещё одному однострочному решению, где этот результат становится входными данными для функции gcd.

Хекстра счёл такое решение предпочтительным.

По фану с Фортраном

В первом видео Хекстра посчитал решение, написанное на Fortran, худшим, потому что оно тоже было заполнено кучей формальностей и усложнений. Но он уточнил, что «это первый кусок кода на Фортране, который я написал в своей жизни, так что, скорее всего, есть лучшие решения… После 40 минут отладки и запуска этого кода я решил, что пора закругляться».

Но в последующем видео Хекстра признался, что получил потрясающий ответ от сообщества Fortran. Джейкоб Уильямс, программист Фортрана и орбитальный механик в Космическом центре НАСА имени Джонсона, ответил, что ему для решения задачи потребовалось около 5 минут (с gcd из кода rosetta). 

Также видео Хекстры вызвало отклик у Милана Курчича, давнего программиста на Фортране и автора книги «Современный Фортран». Курик поблагодарил Хекстру за включение Фортрана в свой обзор и предложил альтернативное решение, которое включает функцию наибольшего общего знаменателя:

Оказалось, что временная переменная res не нужна, так как результат можно просто вернуть, не присваивая ему отдельного значения. И некоторые обновления синтаксиса в Фортране теперь позволяют указывать длину списка с помощью одного лишь двоеточия вместо громоздкой переменной numsSize. Это позволяет избавиться от целой строки кода. Итого вместо огромного кода можно прийти к всего двум строкам в теле функции!

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


Что ещё интересного есть в блоге Cloud4Y

→ Как открыть сейф с помощью ручки

→ OpenCat — создай своего робокотика

→ Как распечатать цветной механический телевизор на 3D-принтере

→ WD-40: средство, которое может почти всё

→ Изобретатели, о которых забыли

Подписывайтесь на наш Telegram-канал, чтобы не пропустить очередную статью. Пишем только по делу. А ещё напоминаем про второй сезон нашего сериала ITить-колотить. Его можно посмотреть на YouTube и ВКонтакте.

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


  1. IvanSTV
    05.10.2022 12:14
    +3

    даже по этому куску видно, что фортранчик-то нехило развивали с 1994, когда я на нем последний раз что-то написал :)


  1. baldr
    05.10.2022 12:45
    +20

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

    Почеу никто не решает задачу "из жизни"? Например - для 10000 пользователей выбрать их любимые числа из базы. Ок, найдем их НОД, отправим письмом каждому по почте в pdf-вложении, а потом покажем на страничке. Все это - каждый день в 10:00.

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


    1. DistortNeo
      05.10.2022 13:03
      +20

      Мне ещё не понятно, какой смысл смотреть на обёртки над библиотечной GCD?


      1. Akon32
        05.10.2022 14:56
        +1

        Сравнить синтаксис языков.

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


        1. baldr
          05.10.2022 15:02
          +3

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

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


          1. Kelbon
            05.10.2022 21:39

            Что мешает изменить реализацию стандартной библиотеки? Если вдруг в будущем появится более эффективный способ


    1. IvanSTV
      05.10.2022 13:50

      Например - для 10000 пользователей выбрать их любимые числа из базы. Ок, найдем их НОД, отправим письмом каждому по почте в pdf-вложении, а потом покажем на страничке. Все это - каждый день в 10:00.

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


      1. Opiumforme
        06.10.2022 12:29

        Для этого и придумали свои почтовые сервера ????


    1. rutexd
      06.10.2022 12:28

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

      При желании можно сделать многое на многом. Работу в базой данных на Haskell, работу с pdf на Ruby и отправку емейла с Julia. При желании зачастую ничто не мешает скрестить одно с другим под видом третьего. Или например какой нибудь Cobol на котором работает некая часть экономической инфраструктуры в Америке. А возможно и не только там...

      Вопрос удобства как исходной точки истины - не самый ведущий к результату. В начале нулевых или даже 80 не было такого обилия литературы и "удобства" среди языков как сейчас. Да и части языков в принципе не было. К тому моменту существующие языки были распространены в меру своих возможностей и на них писали. Несмотря на отсутствие удобств, на написанном руками ассемблере летали на луну. Несмотря на отсутствие удобств, есть ядра Linux и Windows NT/... которые все работают по всему миру на легаси коде написанных на не самых удобных языках при этом стабильно (разные сегментфолты или бсоды не берём в расчёт).

      Языки которые предоставляют базовый функционал общего назначения - подойдут для почти всего. Делегировать на язык как на панацею который решает твои проблемы - ошибочно. Решать их должен программист. Но нынешний программист решать не хочет а хочет удобные ORM а за отсутствием таковых идёт писать статьи или делиться мнением почему язык ххх - в топку.

      Вопрос бизнеса - это вопрос бизнеса. Вопрос удобства - вопрос личного навыка и личного желания.


      1. 0xd34df00d
        06.10.2022 17:39
        +1

        Работу в базой данных на Haskell, работу с pdf на Ruby и отправку емейла с Julia.

        А, кстати, в чём проблема с работой с БД в хаскеле?


        Можно дёргать БД, руками описывая запросы, как я делал и в C++ 15-10-5 лет назад (и, кстати, за счёт ленивости языка получая стриминг бесплатно без всякого геморроя). Можно описать типы и написать deriving (Beamable), и компилятор всё сделает за тебя. Можно упороться по стрелчокам. Куча вариантов на любой вкус.


        1. rutexd
          07.10.2022 02:12

          Так вот именно что куча вариантов. Так вот именно что есть огромное количество возможностей. Полностью согласен с вами. Было бы лишь желание. Чего к сожалению все меньше у программистов и с каждым годом все больше абстракций над слоном и нежеланием разбираться с другими технологиями где можно потратить условно на час больше но получить тоже самое и даже и лучше за счёт меньшего слонохеда. Или получить опыт например.

          Боюсь спросить что будет лет через 20.


          1. kovserg
            07.10.2022 10:06

            каждым годом все больше абстракций над слоном и нежеланием разбираться

            А всё почему: youtu.be/GVXJ0Y8IBb0?t=3025


  1. oleg_shamshura
    05.10.2022 13:21
    +22

    "Как в одной строчке вызвать три готовых функции"


  1. NeoCode
    05.10.2022 13:52
    +3

    В BQN синтаксис шикарный:) Больше всего юникода.

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


    1. SergeiMinaev
      05.10.2022 15:47
      +1

      Согласен. Особенно это заметно в таких языках, как Rust, где нужно много всего выразить. Ещё не хватает раскладки для программистов :) В QWERTY несколько напрягает зажимать шифт для нижнего подчёркивания.


  1. tas
    05.10.2022 15:34
    +2

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


    1. domix32
      06.10.2022 05:56

      скорость выполнения

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


      1. SpiderEkb
        07.10.2022 09:02
        +1

        А вот не факт. Если смотреть скорость выполнения и, главное, использование ресурсов процессора каким-нибудь инструментом тип PEX (Perfomace Explorer) который мы постоянно используем, то там отчетливо видно, что использование "удобных библиотек" в том же С++ ведет к ощутимому росту накладных расходов за счет постоянной работы с динамической памятью - выделение/освобождение и т.п. Что, например, черезмерное использование исключений (особенно пробрасывание их из одной области видимости в другую) ведет к снижению производительности по сравнению со старорежимным возвратом ошибки. Например, на нашей платформе есть понятие "структурированная ошибка" - содержит в себе код + набор параметров. Плюс специальные Message файлы в которых хранится текстовая расшифровка и уровень серьезности ошибки и откуда можно получить через системное API полный текст с подставленными параметрами. Это, например, позволяет хранить стек ошибок в процессе выполнения. И работает быстрее чем исключения (которые, впрочем, тоже есть).

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

        Взять обмен данными между двумя модулями. Есть модный json, который так любят пихать везде. Очень универсально, поддержка в библиотеках для всех языков и т.п. Но вот задумайтесь - вы заранее знаете, что Модуль А может посылать модулю Б всего три типа пакетов, структура каждого из которых известна заранее. Ну так сделаейте датаграммный обмен - в заголовке укажите тип пакета и его длину, а на приеме просто посмотреть заголовок и смапить блок данных в один из трех возможных типов структур. Все. Не надо тратить время и ресурсы сначала на формирование json, потом на его парсинг. Да, не универсально. Но на то и голова дана чтобы каждый раз подумать - а нужна универсальность именно здесь и сейчас? Или можно обойтись без нее и получить выигрыш в эффективности?

        К сожалению, об этом задумываются далеко не все.


        1. Akon32
          07.10.2022 11:38

          Есть модный json, который так любят пихать везде. Очень универсально, поддержка в библиотеках для всех языков и т.п. Но вот задумайтесь - вы заранее знаете, что Модуль А может посылать модулю Б всего три типа пакетов, структура каждого из которых известна заранее. Ну так сделаейте датаграммный обмен - в заголовке укажите тип пакета и его длину, а на приеме просто посмотреть заголовок и смапить блок данных в один из трех возможных типов структур. Все. Не надо тратить время и ресурсы сначала на формирование json, потом на его парсинг.

          А потом структура пакета изменится, поля местами поменяются, и "всё", счастливой отладки, держитесь там.

          Для json есть более быстрые альтернативы - тот же messagepack, да и более удобочитаемые - типа yaml. Но json везде пихают потому, что он более распространён, библиотеку для его поддержки часто даже искать не надо.


          1. baldr
            07.10.2022 12:05
            +1

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

            Если для дебага надо - предусмотрите временное переключение на json. Или выводите куда-то в отдельный буфер и читайте там.

            Если меняются поля или структура - ну тогда меняйте версию пакета или адрес отправки.

            Сериализация структуры в json (строка) медленная, парсинг - тоже медленный. И валидация обязательно нужна по схеме же? Если мы берем тот же yaml - он медленней еще на порядок, хотя более читаемый, да.

            У меня есть сервис куда раз в секунду надо слать MQTT-пакеты. Он поддерживает и json и protobuf. Protobuf - меньше в 3 раза и известного размера. А чтобы сериализовать float-число типа 3.42524574658785678 в json с не более чем 3 числами после запятой - без хаков - никак.


            1. Akon32
              07.10.2022 14:54

              А чем messagepack не бинарный?


              1. baldr
                07.10.2022 15:04

                Согласен, он бинарный. Однако, проблемы остальные те же самые. Даже на его сайте пример в картинке - пакет в 18 байт для того чтобы передать 3 байта информации.


            1. domix32
              07.10.2022 23:05

              Да если кому-то хочется в перфоманс и не хочется в копирование при сериализации, тогда сразу брать какие-нибудь flatbuffers/capn proto.


        1. domix32
          07.10.2022 23:00

          Речь шла про код в рамках задачи описанной в статье, а не про общий концепт замеров. То бишь подноготная всех этих алгоритмов использует старый добрый Евклидов алгоритм и при компиляции до некоторого -Os/-O3 уровня куски ассемблера будут крайне похожи. Поэтому разница между такими имплементациями будет на грани статистической ошибки. А то что не компилируется имеет некоторых оверхэд на поднятие VM/GC и для разных языков будет иметь разные значения. Поэтому и сказал что гонять бенчмарки для такого не имеет особого смысла. Если конечно автор не решит сделать Production Grade версии алгоритма с джейсонами и CI/CD.


  1. koldyr
    05.10.2022 16:20

    Хаскель как всегда вне конкуренции. Пришлось взять листочек чтобы понять почему оно вообще работает. Придумавшему это человеку я бы с удовольствием поставил пиво.


    1. codecity
      05.10.2022 16:39
      +8

      Пришлось взять листочек чтобы понять

      Я думал вне конкуренции - простота и максимальная ясность.


      1. Didimus
        05.10.2022 16:52
        +1

        Тогда нужны паскаль и бейсик. Но их не включили


      1. 0xd34df00d
        05.10.2022 17:58
        +5

        Там нет ничего сложного или неясного, если понимать, что тип r -> (то есть, функции из фиксированного типа) — это тоже монада. Хотя я бы предпочёл написать gcd <$> minimum <*> maximum или хотя бы liftA2 вместо liftM2, потому что монадическая структура тут не нужна.


        Ну и ещё можно начать говорить, что получившийся код на хаскеле полиморфен по контейнерам (и можно искать НОД минимумов-максимумов хоть в списках, хоть в векторах, хоть в чём хочешь), а код на питоне говорит только о списках, но это уже следующий вопрос.


        1. koldyr
          05.10.2022 18:36

          Справедливости ради: реализацию fold все равно надо писать для каждого контейнера. Это сопоставимо с реализацией итератора для контейнера например в Питоне.


          1. 0xd34df00d
            05.10.2022 18:48
            +1

            Да, полностью согласен.


            Кстати, как на питоне будет выглядеть тип функции, которая потребляет любой Foldable итератор, возвращающий любой питоновский аналог Integral a?


            1. ilyapirogov
              05.10.2022 21:03
              +1

              Если под типом функции вы имеете type hints, то, видимо, как-то так:

              from collections.abc import Iterable
              from typing import TypeVar
              
              T = TypeVar('T', int, float, complex)
              
              def func(arg: Iterable[T]) -> T:
                  pass
              


              1. 0xd34df00d
                05.10.2022 21:39
                +1

                Это не совсем то же самое, потому что, как я понимаю, у вас тут список возможных типов фиксирован на четвёртой строке (где вы определяете T). Код на хаскеле же может работать с любым типом, реализующим тайпкласс Integral (включая что угодно, написанное мной потом отдельно).


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


                T = TypeVar('T', int, float, complex)
                
                def findGCD(nums: Iterable[T]) -> T:
                    return math.gcd(min(nums), max(nums))

                ?


                1. Akon32
                  05.10.2022 22:10

                  На питоне типизация динамическая, и смысла в "Iterable[T]" немного. Тем более, что min и max принимают что-то вроде Iterable[T] where T:Comparable, а gcd принимает только Integer (а выясняется это в рантайме).

                  Так что:

                  def findGCD(nums: Iterable[int]) -> int:

                  А лучше даже без этого всего:

                  def findGCD(nums):

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


                  1. 0xd34df00d
                    05.10.2022 22:37
                    +1

                    а gcd принимает только Integer (а выясняется это в рантайме).

                    Почему? Чем какой-нибудь uint32 из numpy хуже?


                    На питоне типизация динамическая, и смысла в "Iterable[T]" немного.

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


                    1. Akon32
                      05.10.2022 23:56

                      Почему? Чем какой-нибудь uint32 из numpy хуже?

                      Действительно, uint32 принимает, а float'ы - нет.

                      Вероятно, целые числа, принимаемые gcd(), соблюдают некий контракт, проверяемый в рантайме, описание которого ещё поискать надо.

                      важно для простоты и максимальной ясности

                      Следующий заголовок на Rust нужен лишь для того, чтобы передать v в замыкание, передаваемое в библиотеку warp:

                      fn with_value<T>(v: T) -> impl Filter<Extract=(T, ), Error=Infallible> + Clone where T: Clone + Send {

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


                      1. AnthonyMikh
                        06.10.2022 02:10

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

                        Или не работает, потому что в функцию передан не тот callable объект. Но вы об этом узнаете только в райнтайме.


                        И да, в коде на Rust ещё и ограничения на потокобезопасность и (глубокое) копирование — вещи, которые в Python просто не имеют смысла.


                      1. Akon32
                        06.10.2022 06:44

                        Или не работает, потому что в функцию передан не тот callable объект

                        После отладки - работает. Вопрос в том, что проще - отладить очевидный (в данном случае) код или написать на ровном месте что-то неочевидное.

                        Конечно, контрпримеры тоже есть, минусы динамической типизации я понимаю и предпочитаю всё-таки статическую.

                        в коде на Rust ещё и ограничения на потокобезопасность и (глубокое) копирование

                        Немного не угадали - фактически как Т используется arc<mutex<T2>>, поэтому глубокого копирования нет, несмотря на Clone.


                      1. 0xd34df00d
                        06.10.2022 02:32
                        +1

                        Следующий заголовок на Rust нужен лишь для того, чтобы передать v в замыкание, передаваемое в библиотеку warp:

                        А причём тут раст?


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

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


                      1. Akon32
                        06.10.2022 06:28

                        Это реальный пример, где статическая типизация скорее мешает, чем помогает.

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


                      1. AnthonyMikh
                        07.10.2022 15:49
                        +1

                        и парадокс в том, что код все равно работает (пусть и после отладки, <...>)

                        Ну то есть не работает.


                1. ilyapirogov
                  05.10.2022 23:31

                  Ну да, это все-таки python, его возможность по типизации очень ограничены.
                  В целом, можно создать свой Protocol:

                  import math
                  from typing import TypeVar, Iterable, Protocol, Any, runtime_checkable
                  
                  @runtime_checkable
                  class Integral(Protocol):
                      def __lt__(self, other: Any) -> bool: ...
                      def __index__(self) -> int: ...
                  
                  T = TypeVar('T', bound=Integral)
                  
                  def findGCD(nums: Iterable[T]) -> T:
                      return math.gcd(min(nums), max(nums))
                  

                  Но это будет работать только с пользовательскими классами. Кроме того это все-равно только type hints, так что проверять тип все равно вручную придется.

                  class CustomInt:
                      val: int
                  
                      def __init__(self, val: int):
                          self.val = val
                  
                      def __lt__(self, other: Any) -> bool:
                          if isinstance(other, CustomInt):
                              return self.val < other.val
                          raise TypeError("other is not CustomInt")
                  
                      def __index__(self) -> int:
                          return self.val
                  
                      def __str__(self) -> str:
                          return str(self.val)
                  
                  
                  if __name__ == '__main__':
                      assert isinstance(1, Integral)
                      assert isinstance(CustomInt(2), Integral)
                      assert not isinstance(2.4, Integral)
                      assert not isinstance("a", Integral)
                  
                      print(findGCD([CustomInt(6), CustomInt(5), CustomInt(7), CustomInt(8), CustomInt(4)])) 
                   # prints 4
                  

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

                  Сломается в рантайме в функции math.gcd(). Вряд-ли с этим можно что-то сделать, кроме как проходится по всем элементам и проверять тип явно.


  1. AndreyDmitriev
    05.10.2022 16:31
    +3

    На LabVIEW будет как-то вот так, тут это тоже библиотечная функция:


  1. wyfinger
    05.10.2022 16:38
    +5

    Без ассемблера никуда не годится!


  1. SuperTEHb
    05.10.2022 17:08
    +2

    Было бы любопытно попутно производительность их всех сравнить.


  1. ksbes
    05.10.2022 17:11
    +1

    А где Brainfuck?
    А вообще видео больше напоминает обзор одной из многих тысяч веток codegolf. Там даже поинтереснее будет.


  1. FanzilAlRawi
    05.10.2022 18:27

    А на PL/1?


  1. SpiderEkb
    05.10.2022 19:52

    Как правильно уже отметили - какой смысл сравнивать синтаксис вызова библиотечных функций?

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


    1. kovserg
      05.10.2022 21:24

      Точно надо на детской задаче демонстрировать. Например:
      Есть два прямоугольника один со сторонами a,b и второй со сторонами c,d
      Написать функцию проверки: можно ли разместить второй внутри первого.


      1. Daddy_Cool
        05.10.2022 22:31

        Можно задачку сделать чуть интересней ;)
        Вершины заданы координатами.
        Размещается ли второй прямоугольник четырехугольник внутри первого?


        1. Akon32
          06.10.2022 00:01

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


        1. kovserg
          06.10.2022 08:13

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


    1. 0xd34df00d
      06.10.2022 02:43
      +1

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


      Но, например, я такое действительно регулярно делаю на примере хаскеля. Скажем, относительно недавно QOI реализовывал [1] [2] (надо бы уже перевести и запостить на хабр, да).


  1. kt97679
    05.10.2022 22:59
    +4

    Делал нечто подобное: github.com/kt97679/tetris Языков меньше — всего 7, но зато задача более сложная — тетрис.


  1. mapcuk
    06.10.2022 10:22
    +3

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

    Решил посмотреть реализацию gcd на Rosetta Stone и нашёл там такой бриллиант на неком Golfscript:

    ;'2706 410'
    ~{.@\%.}do;
    
    Output: 82 

    А есть ли хит-парад у кого самый непонятный код? Мне кажется у Golfscrpt неплохие шансы попасть в ТОП.


    1. baldr
      06.10.2022 10:25

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


  1. 0xf331d34d
    06.10.2022 12:29
    +1

    Странно, что никто ещё не упомянул розетту - https://rosettacode.org/.

    Специальная Вики, которая как раз заточена для сравнения решений разных языков программирования на тривиальных задачах.

    Их там больше тысячи и около 800 языков программирования, включая всеми любимый Brainfuck.


  1. Oxyd
    06.10.2022 13:00

    Что-бы понять рекурсию, нужно понять рекурсию.

    На lua (как ни странно нет math.gcd())

    #!/usr/bin/env lua
    
    function gcd(a,b)
        if b == 0 then return math.abs(a)
                  else return gcd(b,a%b)
        end
    end
    
    DataSet = {2, 9, 6, 10, 5}
    
    print (gcd(
                  (math.min
                      (table.unpack(DataSet))
                  )
                      ,
                  (math.max
                      (table.unpack(DataSet))
                  )
              )
          )

    На REXX, тоже самое. Разумеется там тоже нет gcd. Но дедушка старенький, дедушке простительно. ;-)

    #!/usr/bin/env rexx
    
    /* Find min, max and gcd of min and max */
    
    DataSet="2, 9, 6, 10, 5"
    
    INTERPRET "Min=min("Dataset")"
    INTERPRET "Max=max("Dataset")"
    SAY gcd(Min, Max)
    
    EXIT
    
    gcd: procedure
    PARSE ARG a,b
    IF b = 0 THEN RETURN abs(a)
    RETURN gcd(b,a//b)

    Да, бобик регистронезависимый язык и в нём нет зарезервированных слов. Так min() здесь и функция и Min переменная. Далее язык понимает по контексту. Но злоупотреблять этим конечно не стоит.


    1. Akon32
      06.10.2022 13:20

      math.min(table.unpack(DataSet))

      Распаковка таблицы на стек? Боюсь, это не всегда сработает.


  1. kochetkov-ma
    08.10.2022 00:49

    Приходишь такой на первый рабочий день а там весь проект на APL

    https://habrastorage.org/r/w1560/getpro/habr/upload_files/106/d14/a84/106d14a84d7abb7916c394b5a27b82af.png

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