Видеоблогер Конор Хекстра использовал разные языки программирования, чтобы решить одну и ту же задачу. Попутно выяснилось, что у Фортрана полно поклонников.
![](https://habrastorage.org/getpro/habr/upload_files/304/f2e/e30/304f2ee300bbe9916e7aee9ebd7d0943.png)
На 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 уже есть встроенные функции для всех необходимых операций: для нахождения наименьшего числа, наибольшего числа, даже для «наибольшего общего делителя».
![](https://habrastorage.org/getpro/habr/upload_files/0a2/9f1/2e6/0a29f12e66d2a91807b95af8585eb3df.png)
Но всё стало выглядеть абсолютно по-другому, когда Хекстра добрался до функционального языка Haskell. Его функция liftM2
отображает значения минимума и максимума для входных данных функции gcd
— и все это в одной строке.
И самое главное, поскольку Haskell использовал стиль программирования “Tacit” или point-free (стиль программирования без промежуточных переменных, предполагает использование конвейерных функций и комбинаторов), который не указывает аргументы в определениях функций, решение Haskell даже не нуждалось в упоминании массива чисел.
![](https://habrastorage.org/getpro/habr/upload_files/36e/7f7/4e4/36e7f74e421c59e594d4f6cc7af3e8ed.png)
Низкоуровневый язык программирования D требует, чтобы функции были импортированы. Но он хотя бы использует универсальный синтаксис для вызова функций знакомым методом из объектно-ориентированного программирования. Хекстра посчитал это довольно милым.
![](https://habrastorage.org/getpro/habr/upload_files/ba9/0b7/bc8/ba90b7bc86d496004b50b358540bca6b.png)
Хекстра жаловался, что в Rust и в C++ слишком много «формальностей». Обе функции, и min, и max, требуют использования iter()
для прохождения итераций по каждому значению в списке чисел, и unwrap()
для извлечения значения из более сложного модуля Result, который включает информацию об обработке ошибок.
И, как заметил Хекстра: «чтобы получить доступ к функции gcd приходится преодолеть кучу лишнего в пространстве имён num::integer::
. Но в остальном это очень хорошее решение».
![](https://habrastorage.org/getpro/habr/upload_files/a66/86e/36c/a6686e36cb2fb6c39909eebbb29eb5cc.png)
Так как Хекстра является поклонником языков программирования массивов, в его исследовании нашлись и более существенные различия. Решение в APL (справа от розовой стрелки) состоит всего из пяти символов. (Первые два символа находят максимальное значение, последние два символа находят минимальное значение, а зеленая буква v в середине находит их наибольший общий делитель.)
![](https://habrastorage.org/getpro/habr/upload_files/106/d14/a84/106d14a84d7abb7916c394b5a27b82af.png)
Похожая структура есть и в языке программирования J (ещё один язык программирования массивов, но J является усовершенствованной версией APL, в которой используются диаграммы ASCII, а не символы Unicode»). Функция нахождения максимума представлена здесь символом “>”, а функция поиска минимума — “<”.
![](https://habrastorage.org/getpro/habr/upload_files/daf/fc6/cb2/daffc6cb2fb85193424700ff62874b58.png)
Чем дальше, тем всё более экзотическими становились решения. BQN из семейства APL (согласно APL Wiki) всё ещё является языком, управляемым массивами, но он использует свой уникальный набор символов. Поскольку в нём нет встроенной функции для нахождения наибольшего общего делителя, эта функция должна быть определена в отдельной строке кода.
Хекстра признался, что просто скопировал код из онлайн-источника. Как только функция стала определена в первой строке, появилась возможность вызвать её, как часть решения, во второй строке.
![](https://habrastorage.org/getpro/habr/upload_files/5e3/337/0f8/5e33370f8052aac2cac40ed2571527ec.png)
Соревнование со зрителями
Судя по реакции аудитории, им понравились соревнования языков, и они охотно делились собственным опытом.
“Некоторые интересовались решением для Cobol, так что я решил попробовать свои силы: https://t.co/Pi7OSCdeIl Всего-то 91 строка…”
На данный момент видео получило сотни комментариев, в которых предлагались лучшие варианты решений.
Решение на языке Julia был похоже на решение для Python, только с оператором «splat
» (в данном случае троеточие), означающим, что будет проверено более одного значения:
![](https://habrastorage.org/getpro/habr/upload_files/f12/47c/37c/f1247c37c0a3f3eef5874ba3deb719d8.png)
Но зрители предложили более простой синтаксис. В Julia есть две встроенные функции поиска минимума и максимума, которые не требуют многоточия. Также есть одна функция, которая возвращает оба значения “extrema”. А затем решение стало еще проще, когда Хекстра взял оператор склеивания функций для создания point-free решения (с функцией сбора, преобразующей два значения в формат списка, который можно передать в gcd
).
![](https://habrastorage.org/getpro/habr/upload_files/f00/aa1/154/f00aa1154ddd27ac99f8365af1af6b27.png)
«На мой взгляд, это самое красивое решение из всех, — добавил Хекстра — отчасти потому, что оно демонстрирует элегантность point-free решений.»
Затем зрители предложили ещё несколько изящных улучшений для решения Хекстры на Raku (язык, ранее известный как Perl 6). В Raku gcd
— это «инфиксная» функция, которую можно поместить между двумя значениями аналогично математическим операторам (например, плюс или минус). И тогда два значения, между которых его поместили, могут быть результатами методов объектного стиля, вызываемых массивом чисел.
![](https://habrastorage.org/getpro/habr/upload_files/8f2/56f/046/8f256f046a824b23fc252694e981b3f1.png)
По иронии судьбы, решение Pharo Smalltalk оказалось похожим на решение Raku (со своей собственной функцией gcd:
, которая появлялась как «инфиксный» оператор между двумя функциями, возвращающими минимум и максимум).
![](https://habrastorage.org/getpro/habr/upload_files/4b2/856/9f9/4b28569f917a3887dab13d4ea417bd74.png)
Но зрители отметили, что у Raku также есть специальная функция minmax
, которая возвращает оба значения одновременно, что приводит к ещё одному однострочному решению, где этот результат становится входными данными для функции gcd
.
![](https://habrastorage.org/getpro/habr/upload_files/b66/272/f2a/b66272f2add98847e53e250158d15c97.png)
Хекстра счёл такое решение предпочтительным.
По фану с Фортраном
В первом видео Хекстра посчитал решение, написанное на Fortran, худшим, потому что оно тоже было заполнено кучей формальностей и усложнений. Но он уточнил, что «это первый кусок кода на Фортране, который я написал в своей жизни, так что, скорее всего, есть лучшие решения… После 40 минут отладки и запуска этого кода я решил, что пора закругляться».
![](https://habrastorage.org/getpro/habr/upload_files/41d/37a/22b/41d37a22b43eedf26da47b9f80a8ae29.png)
![](https://habrastorage.org/getpro/habr/upload_files/397/ef5/e49/397ef5e49c22ec04836d78da59be948f.png)
Но в последующем видео Хекстра признался, что получил потрясающий ответ от сообщества Fortran. Джейкоб Уильямс, программист Фортрана и орбитальный механик в Космическом центре НАСА имени Джонсона, ответил, что ему для решения задачи потребовалось около 5 минут (с gcd из кода rosetta).
Также видео Хекстры вызвало отклик у Милана Курчича, давнего программиста на Фортране и автора книги «Современный Фортран». Курик поблагодарил Хекстру за включение Фортрана в свой обзор и предложил альтернативное решение, которое включает функцию наибольшего общего знаменателя:
![](https://habrastorage.org/getpro/habr/upload_files/708/557/0fc/7085570fc52416571dac987fb0847ebb.png)
Оказалось, что временная переменная res
не нужна, так как результат можно просто вернуть, не присваивая ему отдельного значения. И некоторые обновления синтаксиса в Фортране теперь позволяют указывать длину списка с помощью одного лишь двоеточия вместо громоздкой переменной numsSize
. Это позволяет избавиться от целой строки кода. Итого вместо огромного кода можно прийти к всего двум строкам в теле функции!
Официальный канал Fortran в Твиттере даже ретвитнул первоначальный твит Хекстры с видео, с комментарием, что они собираются работать над улучшением своих учебных пособий в будущем. По мнению инженера, именно так должно работать сообщество, если оно хочет привлечь новых людей к своему языку.
Что ещё интересного есть в блоге Cloud4Y
→ Как открыть сейф с помощью ручки
→ OpenCat — создай своего робокотика
→ Как распечатать цветной механический телевизор на 3D-принтере
→ WD-40: средство, которое может почти всё
→ Изобретатели, о которых забыли
Подписывайтесь на наш Telegram-канал, чтобы не пропустить очередную статью. Пишем только по делу. А ещё напоминаем про второй сезон нашего сериала ITить-колотить. Его можно посмотреть на YouTube и ВКонтакте.
Комментарии (59)
baldr
05.10.2022 12:45+20Все эти челленджи с многими языками - только и годятся что для видео и впечатляющих графиков.
Почеу никто не решает задачу "из жизни"? Например - для 10000 пользователей выбрать их любимые числа из базы. Ок, найдем их НОД, отправим письмом каждому по почте в pdf-вложении, а потом покажем на страничке. Все это - каждый день в 10:00.
Интересно - сколько тогда языков дойдет до финиша и сколько реально будут удобны для этого. А также будет видно насколько проще иметь несколько языков в большом проекте, если используешь готовые библиотеки.
DistortNeo
05.10.2022 13:03+20Мне ещё не понятно, какой смысл смотреть на обёртки над библиотечной GCD?
Akon32
05.10.2022 14:56+1Сравнить синтаксис языков.
Мне кажется странным, что gcd есть во многих стандартных библиотеках. По моему опыту, это в основном учебная задача.
Сразу видно, на кого рассчитаны языки.baldr
05.10.2022 15:02+3Не только учебная. В криптографии вполне может использоваться, например, для нахождения взаимно простых чисел.
Возможно сейчас напрямую уже вычисляется более эффективным способом внутри соответствующих библиотек, но что попало в стандартую библиотеку - там и остается.
Kelbon
05.10.2022 21:39Что мешает изменить реализацию стандартной библиотеки? Если вдруг в будущем появится более эффективный способ
IvanSTV
05.10.2022 13:50Например - для 10000 пользователей выбрать их любимые числа из базы. Ок, найдем их НОД, отправим письмом каждому по почте в pdf-вложении, а потом покажем на страничке. Все это - каждый день в 10:00.
закончится тем, что адрес такого экспериментатора все почтовые сервисы отправят в баню. Такую бессмысленную активность любой робот на почтовом серваке определит как спам :)
rutexd
06.10.2022 12:28Ваш вопрос слишком материалистический и делегирующий. Базы данных и pdf с почтой это далеко не то, зачем половина языков была создана. Не беря совсем частные случаи, конечно.
При желании можно сделать многое на многом. Работу в базой данных на Haskell, работу с pdf на Ruby и отправку емейла с Julia. При желании зачастую ничто не мешает скрестить одно с другим под видом третьего. Или например какой нибудь Cobol на котором работает некая часть экономической инфраструктуры в Америке. А возможно и не только там...
Вопрос удобства как исходной точки истины - не самый ведущий к результату. В начале нулевых или даже 80 не было такого обилия литературы и "удобства" среди языков как сейчас. Да и части языков в принципе не было. К тому моменту существующие языки были распространены в меру своих возможностей и на них писали. Несмотря на отсутствие удобств, на написанном руками ассемблере летали на луну. Несмотря на отсутствие удобств, есть ядра Linux и Windows NT/... которые все работают по всему миру на легаси коде написанных на не самых удобных языках при этом стабильно (разные сегментфолты или бсоды не берём в расчёт).
Языки которые предоставляют базовый функционал общего назначения - подойдут для почти всего. Делегировать на язык как на панацею который решает твои проблемы - ошибочно. Решать их должен программист. Но нынешний программист решать не хочет а хочет удобные ORM а за отсутствием таковых идёт писать статьи или делиться мнением почему язык ххх - в топку.
Вопрос бизнеса - это вопрос бизнеса. Вопрос удобства - вопрос личного навыка и личного желания.
0xd34df00d
06.10.2022 17:39+1Работу в базой данных на Haskell, работу с pdf на Ruby и отправку емейла с Julia.
А, кстати, в чём проблема с работой с БД в хаскеле?
Можно дёргать БД, руками описывая запросы, как я делал и в C++ 15-10-5 лет назад (и, кстати, за счёт ленивости языка получая стриминг бесплатно без всякого геморроя). Можно описать типы и написать deriving (Beamable), и компилятор всё сделает за тебя. Можно упороться по стрелчокам. Куча вариантов на любой вкус.
rutexd
07.10.2022 02:12Так вот именно что куча вариантов. Так вот именно что есть огромное количество возможностей. Полностью согласен с вами. Было бы лишь желание. Чего к сожалению все меньше у программистов и с каждым годом все больше абстракций над слоном и нежеланием разбираться с другими технологиями где можно потратить условно на час больше но получить тоже самое и даже и лучше за счёт меньшего слонохеда. Или получить опыт например.
Боюсь спросить что будет лет через 20.
kovserg
07.10.2022 10:06каждым годом все больше абстракций над слоном и нежеланием разбираться
А всё почему: youtu.be/GVXJ0Y8IBb0?t=3025
NeoCode
05.10.2022 13:52+3В BQN синтаксис шикарный:) Больше всего юникода.
На самом деле в программировании реально не хватает тех 32 спецсимволов, которые есть в ascii... особенно разных скобок. Кто знает, если бы история пошла немного по другому пути и вместо первых 32 байт, где большинство символов - невидимые управляющие, были бы дополнительные печатные спецсимволы, возможно языки программирования были бы гораздо выразительнее...
SergeiMinaev
05.10.2022 15:47+1Согласен. Особенно это заметно в таких языках, как Rust, где нужно много всего выразить. Ещё не хватает раскладки для программистов :) В QWERTY несколько напрягает зажимать шифт для нижнего подчёркивания.
tas
05.10.2022 15:34+2Понятно, что математические функции можно реализовать по сути на всех языках. Было бы здорово в подобном обзоре все таки привязываться к характеристике, ценной для конечного пользователя, вроде "удобство кодирования", "простота чтения кода", "скорость выполнения" и т.д. Оценка атрибутов вроде "удобство кодирования" и "простота чтения кода" является дискуссионной, однако их все равно можно оценить в рамках своего imho.
domix32
06.10.2022 05:56скорость выполнения
Да ну в большинстве случаев это будет скучным занятием, как минимум для компилируемых языков ибо там разница в полпроцента чисто из шумов будет.
SpiderEkb
07.10.2022 09:02+1А вот не факт. Если смотреть скорость выполнения и, главное, использование ресурсов процессора каким-нибудь инструментом тип PEX (Perfomace Explorer) который мы постоянно используем, то там отчетливо видно, что использование "удобных библиотек" в том же С++ ведет к ощутимому росту накладных расходов за счет постоянной работы с динамической памятью - выделение/освобождение и т.п. Что, например, черезмерное использование исключений (особенно пробрасывание их из одной области видимости в другую) ведет к снижению производительности по сравнению со старорежимным возвратом ошибки. Например, на нашей платформе есть понятие "структурированная ошибка" - содержит в себе код + набор параметров. Плюс специальные Message файлы в которых хранится текстовая расшифровка и уровень серьезности ошибки и откуда можно получить через системное API полный текст с подставленными параметрами. Это, например, позволяет хранить стек ошибок в процессе выполнения. И работает быстрее чем исключения (которые, впрочем, тоже есть).
Также, любая универсальность не бесплатна. Алгоритм, написанный под конкретную задачу с учетом всех граничных условий и работающий с конкретными структурами и типами данных будет содержать меньше оверкода и работать быстрее, нежели алгоритм "на все случаи жизни".
Взять обмен данными между двумя модулями. Есть модный json, который так любят пихать везде. Очень универсально, поддержка в библиотеках для всех языков и т.п. Но вот задумайтесь - вы заранее знаете, что Модуль А может посылать модулю Б всего три типа пакетов, структура каждого из которых известна заранее. Ну так сделаейте датаграммный обмен - в заголовке укажите тип пакета и его длину, а на приеме просто посмотреть заголовок и смапить блок данных в один из трех возможных типов структур. Все. Не надо тратить время и ресурсы сначала на формирование json, потом на его парсинг. Да, не универсально. Но на то и голова дана чтобы каждый раз подумать - а нужна универсальность именно здесь и сейчас? Или можно обойтись без нее и получить выигрыш в эффективности?
К сожалению, об этом задумываются далеко не все.
Akon32
07.10.2022 11:38Есть модный json, который так любят пихать везде. Очень универсально, поддержка в библиотеках для всех языков и т.п. Но вот задумайтесь - вы заранее знаете, что Модуль А может посылать модулю Б всего три типа пакетов, структура каждого из которых известна заранее. Ну так сделаейте датаграммный обмен - в заголовке укажите тип пакета и его длину, а на приеме просто посмотреть заголовок и смапить блок данных в один из трех возможных типов структур. Все. Не надо тратить время и ресурсы сначала на формирование json, потом на его парсинг.
А потом структура пакета изменится, поля местами поменяются, и "всё", счастливой отладки
, держитесь там.Для json есть более быстрые альтернативы - тот же messagepack, да и более удобочитаемые - типа yaml. Но json везде пихают потому, что он более распространён, библиотеку для его поддержки часто даже искать не надо.
baldr
07.10.2022 12:05+1Вообще, сеньор выше, все-таки, прав. Если два робота обмениваются информацией - то пусть бинарники шлют. И быстрее сериализация, и меньше размер, и тестировать проще, и валидация при передаче сразу есть.
Если для дебага надо - предусмотрите временное переключение на json. Или выводите куда-то в отдельный буфер и читайте там.
Если меняются поля или структура - ну тогда меняйте версию пакета или адрес отправки.
Сериализация структуры в json (строка) медленная, парсинг - тоже медленный. И валидация обязательно нужна по схеме же? Если мы берем тот же yaml - он медленней еще на порядок, хотя более читаемый, да.
У меня есть сервис куда раз в секунду надо слать MQTT-пакеты. Он поддерживает и json и protobuf. Protobuf - меньше в 3 раза и известного размера. А чтобы сериализовать float-число типа 3.42524574658785678 в json с не более чем 3 числами после запятой - без хаков - никак.
domix32
07.10.2022 23:05Да если кому-то хочется в перфоманс и не хочется в копирование при сериализации, тогда сразу брать какие-нибудь flatbuffers/capn proto.
domix32
07.10.2022 23:00Речь шла про код в рамках задачи описанной в статье, а не про общий концепт замеров. То бишь подноготная всех этих алгоритмов использует старый добрый Евклидов алгоритм и при компиляции до некоторого -Os/-O3 уровня куски ассемблера будут крайне похожи. Поэтому разница между такими имплементациями будет на грани статистической ошибки. А то что не компилируется имеет некоторых оверхэд на поднятие VM/GC и для разных языков будет иметь разные значения. Поэтому и сказал что гонять бенчмарки для такого не имеет особого смысла. Если конечно автор не решит сделать Production Grade версии алгоритма с джейсонами и CI/CD.
koldyr
05.10.2022 16:20Хаскель как всегда вне конкуренции. Пришлось взять листочек чтобы понять почему оно вообще работает. Придумавшему это человеку я бы с удовольствием поставил пиво.
codecity
05.10.2022 16:39+8Пришлось взять листочек чтобы понять
Я думал вне конкуренции - простота и максимальная ясность.
0xd34df00d
05.10.2022 17:58+5Там нет ничего сложного или неясного, если понимать, что тип
r ->
(то есть, функции из фиксированного типа) — это тоже монада. Хотя я бы предпочёл написатьgcd <$> minimum <*> maximum
или хотя быliftA2
вместоliftM2
, потому что монадическая структура тут не нужна.Ну и ещё можно начать говорить, что получившийся код на хаскеле полиморфен по контейнерам (и можно искать НОД минимумов-максимумов хоть в списках, хоть в векторах, хоть в чём хочешь), а код на питоне говорит только о списках, но это уже следующий вопрос.
koldyr
05.10.2022 18:36Справедливости ради: реализацию fold все равно надо писать для каждого контейнера. Это сопоставимо с реализацией итератора для контейнера например в Питоне.
0xd34df00d
05.10.2022 18:48+1Да, полностью согласен.
Кстати, как на питоне будет выглядеть тип функции, которая потребляет любой
Foldableитератор, возвращающий любой питоновский аналогIntegral a
?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
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))
?
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 объект другого типа с реализованными арифметическими операциями, и с хакнутыми метаданными о типе - на случай явной проверки типа. Ну, такая замена тайпклассов утиной типизацией получается.
0xd34df00d
05.10.2022 22:37+1а gcd принимает только Integer (а выясняется это в рантайме).
Почему? Чем какой-нибудь uint32 из numpy хуже?
На питоне типизация динамическая, и смысла в "Iterable[T]" немного.
Динамической типизации не существует, это как «сухая вода». Но смысл как раз в том, чтобы заранее, до запуска программы узнавать, может программа иметь смысл, или она его совсем не имеет, и именно это важно для простоты и максимальной ясности, о которой говорилось пару комментариев наверх.
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 корректно передастся в другой поток. Но дело в том, что аналогичный код на питоне без этих сотен символов тоже работает. Статическая типизация - это скорее про надёжность, но не про простоту.
AnthonyMikh
06.10.2022 02:10Но дело в том, что аналогичный код на питоне без этих сотен символов тоже работает.
Или не работает, потому что в функцию передан не тот callable объект. Но вы об этом узнаете только в райнтайме.
И да, в коде на Rust ещё и ограничения на потокобезопасность и (глубокое) копирование — вещи, которые в Python просто не имеют смысла.
Akon32
06.10.2022 06:44Или не работает, потому что в функцию передан не тот callable объект
После отладки - работает. Вопрос в том, что проще - отладить очевидный (в данном случае) код или написать на ровном месте что-то неочевидное.
Конечно, контрпримеры тоже есть, минусы динамической типизации я понимаю и предпочитаю всё-таки статическую.
в коде на Rust ещё и ограничения на потокобезопасность и (глубокое) копирование
Немного не угадали - фактически как Т используется arc<mutex<T2>>, поэтому глубокого копирования нет, несмотря на Clone.
0xd34df00d
06.10.2022 02:32+1Следующий заголовок на Rust нужен лишь для того, чтобы передать v в замыкание, передаваемое в библиотеку warp:
А причём тут раст?
Но дело в том, что аналогичный код на питоне без этих сотен символов тоже работает. Статическая типизация — это скорее про надёжность, но не про простоту.
Так понимать, что происходит в коде, с типами тоже легче (если типы нормально сделаны). Я не знаю раста, поэтому мне тяжело сказать, насколько строка выше очевидна для среднего растомана, но типы в хаскеле мне вполне помогают.
Akon32
06.10.2022 06:28Это реальный пример, где статическая типизация скорее мешает, чем помогает.
Для растомана, может, это и очевидно, также, как для ассемблерщика очевидно писать простыни кода, но факт в том, что на практике рабочий код можно получить гораздо проще - вон, в питоне этого всего просто нет, в java нет, и парадокс в том, что код все равно работает (пусть и после отладки, а не сразу).
AnthonyMikh
07.10.2022 15:49+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(). Вряд-ли с этим можно что-то сделать, кроме как проходится по всем элементам и проверять тип явно.
SpiderEkb
05.10.2022 19:52Как правильно уже отметили - какой смысл сравнивать синтаксис вызова библиотечных функций?
Почему бы не вщять задачку, требующую скмостоятельной реализации алгоритма и уже на ней сравнивать? И не аотому, сколько строк кода это займет, а по скорости выполнения и количеству используемых ресурсов.
kovserg
05.10.2022 21:24Точно надо на детской задаче демонстрировать. Например:
Есть два прямоугольника один со сторонами a,b и второй со сторонами c,d
Написать функцию проверки: можно ли разместить второй внутри первого.Daddy_Cool
05.10.2022 22:31Можно задачку сделать чуть интересней ;)
Вершины заданы координатами.
Размещается ли второйпрямоугольникчетырехугольник внутри первого?Akon32
06.10.2022 00:01Тогда рекомендую проверять N-гранники с N порядка миллиарда в многомерном пространстве, чтоб уж какие-нибудь R-деревья навернуть, а не сводить весь алгоритм к логическому выражению.
kovserg
06.10.2022 08:13Пересечение полигонов не так интересно и постановка задачи сложнее. Тут тоже можно прямоугольники вращать произвольным образом.
0xd34df00d
06.10.2022 02:43+1
kt97679
05.10.2022 22:59+4Делал нечто подобное: github.com/kt97679/tetris Языков меньше — всего 7, но зато задача более сложная — тетрис.
mapcuk
06.10.2022 10:22+3Ожидал реализацию без встроенных функций, синтаксис лучше смотреть на learnxinyminutes.com там как-то нагляднее.
Решил посмотреть реализацию gcd на Rosetta Stone и нашёл там такой бриллиант на неком Golfscript:
;'2706 410' ~{.@\%.}do; Output: 82
А есть ли хит-парад у кого самый непонятный код? Мне кажется у Golfscrpt неплохие шансы попасть в ТОП.
baldr
06.10.2022 10:25Рискну предположить что в машинных кодах для какого-то малоизвестного процессора будет еще веселее.
0xf331d34d
06.10.2022 12:29+1Странно, что никто ещё не упомянул розетту - https://rosettacode.org/.
Специальная Вики, которая как раз заточена для сравнения решений разных языков программирования на тривиальных задачах.
Их там больше тысячи и около 800 языков программирования, включая всеми любимый Brainfuck.
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
переменная. Далее язык понимает по контексту. Но злоупотреблять этим конечно не стоит.Akon32
06.10.2022 13:20math.min(table.unpack(DataSet))
Распаковка таблицы на стек? Боюсь, это не всегда сработает.
kochetkov-ma
08.10.2022 00:49Приходишь такой на первый рабочий день а там весь проект на APL
Это вообще законно? Я понимаю, что это это язык для определенных целей, но как это читают люди, регулярки и то проще читать...
IvanSTV
даже по этому куску видно, что фортранчик-то нехило развивали с 1994, когда я на нем последний раз что-то написал :)