Комментарии 64
даже по этому куску видно, что фортранчик-то нехило развивали с 1994, когда я на нем последний раз что-то написал :)
Все эти челленджи с многими языками - только и годятся что для видео и впечатляющих графиков.
Почеу никто не решает задачу "из жизни"? Например - для 10000 пользователей выбрать их любимые числа из базы. Ок, найдем их НОД, отправим письмом каждому по почте в pdf-вложении, а потом покажем на страничке. Все это - каждый день в 10:00.
Интересно - сколько тогда языков дойдет до финиша и сколько реально будут удобны для этого. А также будет видно насколько проще иметь несколько языков в большом проекте, если используешь готовые библиотеки.
Мне ещё не понятно, какой смысл смотреть на обёртки над библиотечной GCD?
Сравнить синтаксис языков.
Мне кажется странным, что gcd есть во многих стандартных библиотеках. По моему опыту, это в основном учебная задача. Сразу видно, на кого рассчитаны языки.
Не только учебная. В криптографии вполне может использоваться, например, для нахождения взаимно простых чисел.
Возможно сейчас напрямую уже вычисляется более эффективным способом внутри соответствующих библиотек, но что попало в стандартую библиотеку - там и остается.
Например - для 10000 пользователей выбрать их любимые числа из базы. Ок, найдем их НОД, отправим письмом каждому по почте в pdf-вложении, а потом покажем на страничке. Все это - каждый день в 10:00.
закончится тем, что адрес такого экспериментатора все почтовые сервисы отправят в баню. Такую бессмысленную активность любой робот на почтовом серваке определит как спам :)
Ваш вопрос слишком материалистический и делегирующий. Базы данных и pdf с почтой это далеко не то, зачем половина языков была создана. Не беря совсем частные случаи, конечно.
При желании можно сделать многое на многом. Работу в базой данных на Haskell, работу с pdf на Ruby и отправку емейла с Julia. При желании зачастую ничто не мешает скрестить одно с другим под видом третьего. Или например какой нибудь Cobol на котором работает некая часть экономической инфраструктуры в Америке. А возможно и не только там...
Вопрос удобства как исходной точки истины - не самый ведущий к результату. В начале нулевых или даже 80 не было такого обилия литературы и "удобства" среди языков как сейчас. Да и части языков в принципе не было. К тому моменту существующие языки были распространены в меру своих возможностей и на них писали. Несмотря на отсутствие удобств, на написанном руками ассемблере летали на луну. Несмотря на отсутствие удобств, есть ядра Linux и Windows NT/... которые все работают по всему миру на легаси коде написанных на не самых удобных языках при этом стабильно (разные сегментфолты или бсоды не берём в расчёт).
Языки которые предоставляют базовый функционал общего назначения - подойдут для почти всего. Делегировать на язык как на панацею который решает твои проблемы - ошибочно. Решать их должен программист. Но нынешний программист решать не хочет а хочет удобные ORM а за отсутствием таковых идёт писать статьи или делиться мнением почему язык ххх - в топку.
Вопрос бизнеса - это вопрос бизнеса. Вопрос удобства - вопрос личного навыка и личного желания.
Так вот именно что куча вариантов. Так вот именно что есть огромное количество возможностей. Полностью согласен с вами. Было бы лишь желание. Чего к сожалению все меньше у программистов и с каждым годом все больше абстракций над слоном и нежеланием разбираться с другими технологиями где можно потратить условно на час больше но получить тоже самое и даже и лучше за счёт меньшего слонохеда. Или получить опыт например.
Боюсь спросить что будет лет через 20.
каждым годом все больше абстракций над слоном и нежеланием разбираться
А всё почему: youtu.be/GVXJ0Y8IBb0?t=3025
В BQN синтаксис шикарный:) Больше всего юникода.
На самом деле в программировании реально не хватает тех 32 спецсимволов, которые есть в ascii... особенно разных скобок. Кто знает, если бы история пошла немного по другому пути и вместо первых 32 байт, где большинство символов - невидимые управляющие, были бы дополнительные печатные спецсимволы, возможно языки программирования были бы гораздо выразительнее...
Понятно, что математические функции можно реализовать по сути на всех языках. Было бы здорово в подобном обзоре все таки привязываться к характеристике, ценной для конечного пользователя, вроде "удобство кодирования", "простота чтения кода", "скорость выполнения" и т.д. Оценка атрибутов вроде "удобство кодирования" и "простота чтения кода" является дискуссионной, однако их все равно можно оценить в рамках своего imho.
скорость выполнения
Да ну в большинстве случаев это будет скучным занятием, как минимум для компилируемых языков ибо там разница в полпроцента чисто из шумов будет.
А вот не факт. Если смотреть скорость выполнения и, главное, использование ресурсов процессора каким-нибудь инструментом тип PEX (Perfomace Explorer) который мы постоянно используем, то там отчетливо видно, что использование "удобных библиотек" в том же С++ ведет к ощутимому росту накладных расходов за счет постоянной работы с динамической памятью - выделение/освобождение и т.п. Что, например, черезмерное использование исключений (особенно пробрасывание их из одной области видимости в другую) ведет к снижению производительности по сравнению со старорежимным возвратом ошибки. Например, на нашей платформе есть понятие "структурированная ошибка" - содержит в себе код + набор параметров. Плюс специальные Message файлы в которых хранится текстовая расшифровка и уровень серьезности ошибки и откуда можно получить через системное API полный текст с подставленными параметрами. Это, например, позволяет хранить стек ошибок в процессе выполнения. И работает быстрее чем исключения (которые, впрочем, тоже есть).
Также, любая универсальность не бесплатна. Алгоритм, написанный под конкретную задачу с учетом всех граничных условий и работающий с конкретными структурами и типами данных будет содержать меньше оверкода и работать быстрее, нежели алгоритм "на все случаи жизни".
Взять обмен данными между двумя модулями. Есть модный json, который так любят пихать везде. Очень универсально, поддержка в библиотеках для всех языков и т.п. Но вот задумайтесь - вы заранее знаете, что Модуль А может посылать модулю Б всего три типа пакетов, структура каждого из которых известна заранее. Ну так сделаейте датаграммный обмен - в заголовке укажите тип пакета и его длину, а на приеме просто посмотреть заголовок и смапить блок данных в один из трех возможных типов структур. Все. Не надо тратить время и ресурсы сначала на формирование json, потом на его парсинг. Да, не универсально. Но на то и голова дана чтобы каждый раз подумать - а нужна универсальность именно здесь и сейчас? Или можно обойтись без нее и получить выигрыш в эффективности?
К сожалению, об этом задумываются далеко не все.
Есть модный json, который так любят пихать везде. Очень универсально, поддержка в библиотеках для всех языков и т.п. Но вот задумайтесь - вы заранее знаете, что Модуль А может посылать модулю Б всего три типа пакетов, структура каждого из которых известна заранее. Ну так сделаейте датаграммный обмен - в заголовке укажите тип пакета и его длину, а на приеме просто посмотреть заголовок и смапить блок данных в один из трех возможных типов структур. Все. Не надо тратить время и ресурсы сначала на формирование json, потом на его парсинг.
А потом структура пакета изменится, поля местами поменяются, и "всё", счастливой отладки, держитесь там.
Для json есть более быстрые альтернативы - тот же messagepack, да и более удобочитаемые - типа yaml. Но json везде пихают потому, что он более распространён, библиотеку для его поддержки часто даже искать не надо.
Вообще, сеньор выше, все-таки, прав. Если два робота обмениваются информацией - то пусть бинарники шлют. И быстрее сериализация, и меньше размер, и тестировать проще, и валидация при передаче сразу есть.
Если для дебага надо - предусмотрите временное переключение на json. Или выводите куда-то в отдельный буфер и читайте там.
Если меняются поля или структура - ну тогда меняйте версию пакета или адрес отправки.
Сериализация структуры в json (строка) медленная, парсинг - тоже медленный. И валидация обязательно нужна по схеме же? Если мы берем тот же yaml - он медленней еще на порядок, хотя более читаемый, да.
У меня есть сервис куда раз в секунду надо слать MQTT-пакеты. Он поддерживает и json и protobuf. Protobuf - меньше в 3 раза и известного размера. А чтобы сериализовать float-число типа 3.42524574658785678 в json с не более чем 3 числами после запятой - без хаков - никак.
А чем messagepack не бинарный?
Да если кому-то хочется в перфоманс и не хочется в копирование при сериализации, тогда сразу брать какие-нибудь flatbuffers/capn proto.
Если поменялась структура - это уже другой пакет и другой обработчик. Зачем менять местами поля не могу придумать.
Датаграммный обмен вообще не требует библиотеки поддержки. И реализуется везде (малыми при этом затратами) - хоть на STM32 хоть где.
Кроме того, он легко реализуется в виде отдельных модулей - каждый модуль отвечает за обработку своего типа датаграммы. Есть диспетчер, который смотрит на заголовок, берет оттуда тип датаграммы и далее по типу в таблице находит нужный модуль и передает ему датаграмму на обработку. Появился новый тип датаграммы - написали новый модуль, зарегистрировали его в таблице и все заработало. причем, это может быть сделано на горячую, без остановки работы системы в целом и без вмешательства у же работающий код.
Все это требует минимума ресурсов и обеспечивает максимальную скорость.
Естественно, что json и ему подобное тоже нужно. В тех случаях, когда наборы данных могут быть произвольными, их вариативность высока и атомом является конкретное поле, а не пакет целиком. Но тут надо идти от задачи и четко понимать когда что лучше применять. А не пихать один и тот же подход всюду просто потому что других не знаешь.
Вы рассматриваете случай, когда всё идеально работает. А на практике - через 10 лет поменяли компилятор, выравнивание съехало, тесты прошли успешно, БД у заказчика повреждена (Ъ стори). Надёжнее использовать более структурированные данные. Потери ресурсов на msgpack не очень велики, часто они приемлемы. Чистые данные в датаграммах - это скорее годится для жёсткой оптимизации или для коротких проектов без поддержки.
На самом деле, любой нормальный компилятор позволяет управлять выравниванием. Хоть на уровне отдельных структур, хоть на уровне всего проекта в целом.
И интернет и промавтоматизация всякая работает сильно больше 10-ти лет. А там подобные вещи (на уровне транспортных низкоуровневых протоколов) используются сплошь и рядом. Равно как и в различного рода очередях. Самый мой "долгоиграющий" проект начинался в 92-93-м годах. И пройдя через много стадий развития работает до сих пор. И в основе транспорта там как раз датаграммный обмен, причем, с достаточно большим количеством типов датаграмм, их маршрутизацией (контроллер нижнего уровня - контроллер верхнего уровня - микроядро - один или несколько интерфейсных клиентов и обратно). При то, что там еще при добавлении принципиально нового типа устройства, являющегося источником/получателем новых типов датаграмм, нет возможности останавливать или перезагружать систему - все делается "на горячую" - регистрируем в системе новый тип устройства, а потом уже идем на объект и физически его подключаем и оно сразу начинает работать.
Использование структурированных пакетов с привлечением разных библиотек оправданно, когда в проекте в качестве биомассы используют неквалифицированную рабочую силу - чтобы не напортачили, не дай бог. Но тут уже нет смысла говорить о какой-либо эффективности. Если за основу берется подход "херак-херак и в продакшн", то тут вообще говорить не о чем - конечно там никто не будет возиться с профилированием и нагрузочными тестами.
А вот когда речь идет о действительно долгоиграющем проекте, да еще подразумевающим масштабирование, то, если сразу не думать за эффективность, то рано-поздно к вам с этим вопросом придут, уж поверьте опыту - то, что 10 лет назад нормально работало для 15млн клиентов, сейчас начинает тормозить когда клиентов стало 45млн.
Или когда все работает при 1000 устройств в системе, а на 5000 начинаются вылеты по таймаутам и перепосылки потому что принимающая сторона просто не успевает разбирать входящие пакеты и вовремя квитировать корректное их получение.
Речь шла про код в рамках задачи описанной в статье, а не про общий концепт замеров. То бишь подноготная всех этих алгоритмов использует старый добрый Евклидов алгоритм и при компиляции до некоторого -Os/-O3 уровня куски ассемблера будут крайне похожи. Поэтому разница между такими имплементациями будет на грани статистической ошибки. А то что не компилируется имеет некоторых оверхэд на поднятие VM/GC и для разных языков будет иметь разные значения. Поэтому и сказал что гонять бенчмарки для такого не имеет особого смысла. Если конечно автор не решит сделать Production Grade версии алгоритма с джейсонами и CI/CD.
Хаскель как всегда вне конкуренции. Пришлось взять листочек чтобы понять почему оно вообще работает. Придумавшему это человеку я бы с удовольствием поставил пиво.
Пришлось взять листочек чтобы понять
Я думал вне конкуренции - простота и максимальная ясность.
Тогда нужны паскаль и бейсик. Но их не включили
Справедливости ради: реализацию fold все равно надо писать для каждого контейнера. Это сопоставимо с реализацией итератора для контейнера например в Питоне.
Если под типом функции вы имеете type hints, то, видимо, как-то так:
from collections.abc import Iterable
from typing import TypeVar
T = TypeVar('T', int, float, complex)
def func(arg: Iterable[T]) -> T:
pass
На питоне типизация динамическая, и смысла в "Iterable[T]" немного. Тем более, что min и max принимают что-то вроде Iterable[T] where T:Comparable, а gcd принимает только Integer (а выясняется это в рантайме).
Так что:
def findGCD(nums: Iterable[int]) -> int:
А лучше даже без этого всего:
def findGCD(nums):
Впрочем, можно попробовать подсунуть в gcd объект другого типа с реализованными арифметическими операциями, и с хакнутыми метаданными о типе - на случай явной проверки типа. Ну, такая замена тайпклассов утиной типизацией получается.
Почему? Чем какой-нибудь 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 корректно передастся в другой поток. Но дело в том, что аналогичный код на питоне без этих сотен символов тоже работает. Статическая типизация - это скорее про надёжность, но не про простоту.
Но дело в том, что аналогичный код на питоне без этих сотен символов тоже работает.
Или не работает, потому что в функцию передан не тот callable объект. Но вы об этом узнаете только в райнтайме.
И да, в коде на Rust ещё и ограничения на потокобезопасность и (глубокое) копирование — вещи, которые в Python просто не имеют смысла.
Или не работает, потому что в функцию передан не тот callable объект
После отладки - работает. Вопрос в том, что проще - отладить очевидный (в данном случае) код или написать на ровном месте что-то неочевидное.
Конечно, контрпримеры тоже есть, минусы динамической типизации я понимаю и предпочитаю всё-таки статическую.
в коде на Rust ещё и ограничения на потокобезопасность и (глубокое) копирование
Немного не угадали - фактически как Т используется arc<mutex<T2>>, поэтому глубокого копирования нет, несмотря на Clone.
Это реальный пример, где статическая типизация скорее мешает, чем помогает.
Для растомана, может, это и очевидно, также, как для ассемблерщика очевидно писать простыни кода, но факт в том, что на практике рабочий код можно получить гораздо проще - вон, в питоне этого всего просто нет, в java нет, и парадокс в том, что код все равно работает (пусть и после отладки, а не сразу).
Ну да, это все-таки 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(). Вряд-ли с этим можно что-то сделать, кроме как проходится по всем элементам и проверять тип явно.
На LabVIEW будет как-то вот так, тут это тоже библиотечная функция:
format PE GUI 4.0
entry main
section '.data' data readable
aNums dd 183, 892, 445, 281, 863, 530, 602, 235, 356, 898, 639, 291, 582, 526, 941, 972
aNumsCount = ($-aNums)/4
section '.code' code readable executable
main:
mov ecx, (aNumsCount-1)*4
mov eax, [aNums+ecx]
mov ebx, eax
maxmin:
sub ecx, 4
cmp [aNums+ecx], eax
jle notgreater
mov eax, [aNums+ecx]
notgreater:
cmp [aNums+ecx], ebx
jge notless
mov ebx, [aNums+ecx]
notless:
test ecx, ecx
jne maxmin
gcd:
mov ecx, eax
test ebx, ebx
jne cyrcle
jmp exit
cyrcle:
xor edx, edx
div ebx
test edx, edx
je fine
mov eax, ebx; ecx
mov ebx, edx
jmp cyrcle
fine:
mov eax, ebx
exit:
int3 ; result in eax, 3
Было бы любопытно попутно производительность их всех сравнить.
А на PL/1?
Как правильно уже отметили - какой смысл сравнивать синтаксис вызова библиотечных функций?
Почему бы не вщять задачку, требующую скмостоятельной реализации алгоритма и уже на ней сравнивать? И не аотому, сколько строк кода это займет, а по скорости выполнения и количеству используемых ресурсов.
Есть два прямоугольника один со сторонами a,b и второй со сторонами c,d
Написать функцию проверки: можно ли разместить второй внутри первого.
Можно задачку сделать чуть интересней ;)
Вершины заданы координатами.
Размещается ли второй прямоугольник четырехугольник внутри первого?
Тогда рекомендую проверять N-гранники с N порядка миллиарда в многомерном пространстве, чтоб уж какие-нибудь R-деревья навернуть, а не сводить весь алгоритм к логическому выражению.
Ожидал реализацию без встроенных функций, синтаксис лучше смотреть на learnxinyminutes.com там как-то нагляднее.
Решил посмотреть реализацию gcd на Rosetta Stone и нашёл там такой бриллиант на неком Golfscript:
;'2706 410'
~{.@\%.}do;
Output: 82
А есть ли хит-парад у кого самый непонятный код? Мне кажется у Golfscrpt неплохие шансы попасть в ТОП.
Странно, что никто ещё не упомянул розетту - https://rosettacode.org/.
Специальная Вики, которая как раз заточена для сравнения решений разных языков программирования на тривиальных задачах.
Их там больше тысячи и около 800 языков программирования, включая всеми любимый Brainfuck.
Что-бы понять рекурсию, нужно понять рекурсию.
На 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
переменная. Далее язык понимает по контексту. Но злоупотреблять этим конечно не стоит.
Приходишь такой на первый рабочий день а там весь проект на APL
Это вообще законно? Я понимаю, что это это язык для определенных целей, но как это читают люди, регулярки и то проще читать...
вывод: питон гениален и его надо развивать
Решение одной задачи с помощью 16 языков программирования