Стоит ли переходить с Python на Nim ради производительности?

Автор оригинала: Yakko Majuri
  • Перевод
Nim — это сочетание синтаксиса Python и производительности C



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

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

Вот какие выводы я сделал:

  • Этот язык на самом деле популярен среди узкого круга лиц.
  • Возможно, так и должно быть.

Итак, немного расскажу о моём опыте работы с Nim, коротко расскажу об особенностях программирования на нём, а также попробую сравнить его с Python и C. Забегая вперёд, отмечу, что этот язык кажется мне очень перспективным.

Код в студию!


В качестве примера я решил написать на Nim нечто более сложное, чем hello, world:



Вроде бы ничего лишнего, правда? Он кажется настолько простым, что вы без усилий сможете понять, что он делает, даже если вы никогда раньше не слышали о Nim. (Программа выведет: «num: 5 i: 5»)

Итак, давайте разберём то, что откуда-то кажется нам знакомым.

Объявление переменных


Это до боли знакомо разработчикам JavaScript. В то время как некоторые языки используют var, а некоторые используют let, JS и Nim позволяют использовать при объявлении переменных и то, и другое. Однако важно отметить, что в Nim они работают не так, как в JS. Но об этом позже.

Блоки


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

Ключевые слова


Оба цикла, а также оператор if выглядят так, как будто это фрагмент кода на Python. Фактически, всё начиная со строки 5 и далее является кодом на Python (при условии, что у нас определена функция echo).

Так что да, многие ключевые слова и операторы из Python также можно использовать в Nim: not, is, and, or и так далее.

То есть пока мы не видим в Nim ничего особенного: худшая версия Python (с точки зрения
синтаксиса), с учётом того, что для объявления переменных нужно использовать let или var.

На этом можно было бы остановиться, но есть большое «но»: Nim — статически типизированный язык, который работает почти так же быстро, как язык C.

Ну, теперь другой разговор. Давайте проверим это.

Тест производительности




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

Чтобы всё было по-честному, я стандартизировал реализацию на основе решения Leetcode (Вариант 1) и постарался как можно строже придерживаться её на всех трёх языках.

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

Вот результаты для вычисления 40-го числа Фибоначчи:



Да, строго говоря, нельзя назвать эксперимент чистым, но это коррелирует с результатами других энтузиастов, которые делали более серьёзные тесты [1][2][3].

Весь код, который я писал для этой статьи, доступен на GitHub, включая инструкцию о том, как провести этот эксперимент.

Так почему же Nim работает намного быстрее, чем Python?

Ну, я бы сказал, что есть две основные причины:

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

Скорость работы растёт — скорость кодинга падает


Вот что в Python Docs говорится об интерпретируемых языках:
«Интерпретируемые языки обычно имеют более короткий цикл разработки / отладки, чем компилируемые, хотя их программы обычно работают медленнее».

Это хорошее обобщение компромисса между Python и C, например. Всё, что вы можете сделать на Python, вы можете сделать и на C, однако ваша программа на С будет работать во много раз быстрее.

Но вы будете тратить гораздо больше времени на написание и отладку своего кода на C, он будет громоздким и менее читабельным. И именно поэтому C уже не так востребован, а Python популярен. Другими словами, Python гораздо проще (сравнительно, конечно).

Итак, если Python находится на одном конце спектра, а C — на другом, то Nim пытается встать где-то посередине. Он работает намного быстрее, чем Python, но не так сложен для программирования, как C.

Давайте посмотрим на нашу реализацию вычисления чисел Фибоначчи.

С:

#include <stdio.h>
int fibonacci(int n) {
    if (n <= 1) {
        return n;
    } 
    return fibonacci(n-1) + fibonacci(n-2);
}

int main(void) {
    printf("%i", fibonacci(40));
}

Python:

def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(40))

Nim:

proc fibonacci(n: int): int = 
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

echo(fibonacci(40))

Хотя у Nim в синтаксисе процедуры (функции) используется знак «=», в целом писать код намного проще, чем на C.

Может быть, это действительно достойный компромисс? Немного сложнее писать, чем на Python, но работает в десятки раз быстрее. Я мог бы с этим смириться.

Синтаксис Nim


import strformat

# Пример со страницы https://nim-lang.org/

type
  Person = object
    name: string
    age: Natural # Возраст должен быть всегда положительным числом

let people = [
  Person(name: "John", age: 45),
  Person(name: "Kate", age: 30)
]

for person in people:

  echo(fmt"{person.name} is {person.age} years old")

Просто укажу на ключевые особенности.

Переменные


Для объявления переменных используем var, let или const.

var и const работают так же, как в JavaScript, а с let — другая история.

let в JavaScript отличается от var с точки зрения области видимости, а let в Nim обозначает переменную, значение которой не может измениться после инициализации. Мне кажется, это похоже на Swift.

Но разве это не то же самое, что и константа? — спросите вы.

Нет. В Nim различие между const и let заключается в следующем:
Для const компилятор должен иметь возможность определять значение во время компиляции, тогда как для let оно может быть определено во время выполнения.

Пример из документации:

const input = readLine(stdin) # Error: constant expression expected
let input = readLine(stdin)   # всё правильно

Кроме того, переменные можно объявлять и инициализировать так:

var
   a = 1
   b = 2
   c = 3
   x, y = 10 # Обе переменные x и y получили значение 10

Функции


Функции в Nim называются процедурами:

proc procedureName(parameterName: parameterType):returnType =
   return returnVar

Учитывая, что язык во многом похож на Python, процедуры кажутся немного странными, когда вы впервые их видите.

Использовать «=» вместо «{» или «:» явно сбивает с толку. Всё обстоит немного лучше с записью процедуры в одну строку:

proc hello(s: string) = echo s

Вы также можете получать результат выполнения функции:

proc toString(x: int): string =
   result =
       if x < 0: “negative”
       elif x > 0: “positive”
       else: “zero”

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

Вы также можете перегружать процедуры:


proc toString(x: int): string =   
    result =     
        if x < 0: "negative"     
        elif x > 0: "positive"     
        else: "zero"  
proc toString(x: bool): string =   
    result =     
        if x: "yep"     
        else: "nope"
echo toString(true) # Выведет "yep"
echo toString(5) # Выведет "positive"

Условия и циклы


Здесь много общего с Python.

# if true:

# while true:

# for num in nums:

Для перебора списка, например, вместо range() можно использовать countup(start, finish), или countdown(start, finish). Можно поступить ещё проще и использовать for i in start..finish

Пользовательский ввод и вывод


let input = readLine(stdin)
echo input

Если сравнивать с Python, то readLine(stdin) эквивалентно input(), а echo эквивалентно print.

echo можно использовать как со скобками, так и без них.

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

Дополнительные особенности


Объектно-ориентированное программирование


Nim не объектно-ориентированный язык, но в нём реализована минимальная поддержка работы с объектами. До классов Python ему, конечно, далеко.

Макросы


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

Маленький пример:

import macros  macro myMacro(arg: static[int]): untyped =  
   echo arg

myMacro(1 + 2 * 3)

Базовые типы данных


string, char, bool, int, uint и float.

Также можно использовать эти типы:

int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64

Кроме того, в Nim строки являются mutable-типами, в отличие от Python.

Комментарии


В отличие от Python, в Nim для мультистрочных комментариев используется символ «#» в сочетании с «[» и «]».

# a comment#[
a
multi
line
comment
]#

Компиляция JavaScript


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

Итераторы


Итераторы в Nim больше похожи на генераторы в Python:

iterator countup(a, b: int): int =
   var res = a
   while res <= b:
       yield res
       inc(res)

Чувствительность к регистру и нижнее подчёркивание
Nim чувствителен только к регистру первого символа.

То есть, HelloWorld и helloWorld он различает, а helloWorld, helloworld и hello_world — нет. Поэтому без проблем будет работать, например, такая процедура:

proc my_func(s: string) =
   echo myFunc("hello")

Насколько популярен Nim?




У Nim почти 10000 звезд на GitHub. Это явный плюс. Тем не менее, я попытался оценить популярность языка по другим источникам, и, конечно, она не так высока.

Например, Nim даже не упоминался в 2020 Stack Overflow Survey. Я не смог найти вакансии для разработчиков Nim в LinkedIn (даже с географией Worldwide), а поиск по тегу [nim-lang] на Stack Overflow выдал только 349 вопросов (сравните с ~ 1 500 000 для Python или с 270 000 для Swift)

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

Замена Python?


Буду честен: я считаю Nim достаточно крутым языком. Чтобы написать эту статью, я изучил необходимый минимум, но этого хватило. Хотя я не слишком углублялся в него, я планирую использовать Nim в будущем. Лично я большой поклонник Python, но мне также нравятся языки со статической типизацией. Поэтому для меня улучшение производительности в некоторых случаях более чем компенсирует небольшую синтаксическую избыточность.

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

Кроме того, не стоит забывать про язык Go. Я уверен, что многие из вас думали именно об этом во время чтения, и это правильно. Несмотря на то, что синтаксис Nim ближе к синтаксису Python, по производительности он конкурирует именно с языками а-ля «упрощённый C++».
Я в своё время тестировал производительность Go. В частности, для фибоначчи (40) он работал так же быстро, как C.

Но всё-таки: может ли Nim конкурировать с Python? Я очень сомневаюсь в этом. Мы наблюдаем тенденцию роста производительности компьютеров и упрощения программирования. И, как я уже отмечал, даже если Nim предложит хороший компромисс по соотношению синтаксиса и производительности, я не думаю, что этого достаточно, чтобы победить чистый и универсальный Python.
Я общался с одним из разработчиков Nim Core. Он считает, что Nim больше подходит для тех, кто переходит с C ++, чем для питонистов.

Может ли Nim конкурировать с Go? Возможно (если Google «разрешит»). Язык Nim такой же мощный, как и Go. Более того, в Nim лучше реализована поддержка функций C / C ++, в том числе макросы и перегрузка.

Но об этом как-нибудь в следующий раз.



На правах рекламы


Эпичные серверы — это доступные виртуальные серверы с процессорами от AMD, частота ядра CPU до 3.4 GHz. Максимальная конфигурация позволит оторваться на полную — 128 ядер CPU, 512 ГБ RAM, 4000 ГБ NVMe. Поспешите заказать!

VDSina.ru — хостинг серверов
Серверы в Москве и Амстердаме

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

    +2
    если я правильно понял, Nim никак не поддерживает существующие библиотеки/фреймворки на питоне?
      +3

      Да, это ведь не диалект питона

        +2
        понятно, жаль — он попал в середину между го и питоном, и както сильных сторон не видно — для датасаенс не годится, сделать шустрый микросервис — тоже.
          +1
          он попал в середину между С++ и питоном, и с чего вы взяли что не годиться?
          библиотек готовых мало — _любая_ сишная библиотека цепляется напрямую, т.к. Nim через генерацию Си кода компилируется
            –2
            А смысл в этой середине? Медленнее плюсов, без ООП. Быстрее Питона, но тоже без ООП и питоновских библиотек (а без них этот тормозок даром никому не нужен). И совместимости по синтаксису ни с тем, ни с тем.
            Я не изучал Num, он перед компиляцией генерирует код на Си??? Тогда тем более, зачем он? Только для питонистов.
              0

              Ошибка один: он не медленнее плюсов — позиции 9 и 10
              Ошибка два: ООП есть
              Ошибка три: без питоновских библиотек. Ну или для тонких ценителей


              И это только первый абзац.

                0
                Ошибка три: без питоновских библиотек.

                По ссылкам не понятно, эти три библиотеки имеют какое-то отношение к питону вообще? Как я понимаю, здесь релевантна только четвёртая ссылка, которая на nimpy.
                  0

                  Эти три библиотеки реализуют то, для чего вообще в большинстве случаев используется питон (первое что в голову пришло): ML, матрицедробилки, работа с БД.


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

          +9
          Julia тоже не диалект питона, но позволяет почти прозрачно использовать питоновские библиотеки. Соглашусь с первым комментатором — это для многих околообязательная фича языка, заменяющего питон.
            +4
            Проблема Нима — отсутствие Объектов и синтаксис «повсюду» вместо выбора конкретного синтаксической цели (Питон — лаконичность и читабельность).
            Проблема Жулии — счет с 1 (куча гемора из-за этого).

            В общем ничего лучше — написал на Питоне, потом оптимизуй — нет.
              +5
              Проблема Жулии — счет с 1 (куча гемора из-за этого).

              Уже второй раз на хабре вижу это, и совершенно не могу понять почему кто-то так серьёзно относится к выбору первого индекса массива по дефолту. Хоть с 0 в питоне, хоть с 1 в джулии, никаких проблем не замечал. Часто если индексы при обращении важны, то удобнее их брать не 0:n-1, а например -n:n, и джулия в отличие от питона это легко позволяет сделать. Ну а обычно просто нужно что-то типа прохода по массиву — тогда индексы вообще не используются.
              Конечно, недостатки у джулии как языка есть — не бывает ничего идеального — но странно записывать в них выбор дефолтного начала отсчёта индексов.
                +5

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

                  0
                  Видимо, специфика работы отличается, но я ни сразу после перехода на джулию с питона, ни сейчас (спустя ~2 года) начало индексов с 1 и включение границ диапазона ни плюсом ни минусом не считал — абсолютно безразлично, на код по факту не влияет. Разве что изредка радует при обращении к отдельным элементам массива, что a[3] — это на самом деле третий элемент, а не четвёртый :)

                  Правда я с железом на низком уровне не работаю — там, вероятно, действительно лучше с 0 начинать и интерпретировать как сдвиг указателя.
                    +3
                    не очень удобно работать и переносить алгоритмы между питоном и большинством других языков когда индексация отличается. Ну и диапазоны / разделения более понятные когда верхняя граница не включается (например отбросить последние 2 элемента a[:-2], длина диапазона равна разнице между индексами)
                      0
                      например отбросить последние 2 элемента a[:-2]

                      Вот уж что-то, а обозначение отступа с конца отрицательными значениями — явно неудобство питона, я не раз на этом попадался. В нём «a[x]» значит принципиально разные вещи в зависимости от знака x, лучше бы ошибку выдавало. В джулии отступ с конца идёт как «a[end — 2]», и такой неоднозначности нет.
                +1
                Что вы имеете ввиду под «отсутствие Объектов»? В Nim новые типы для хранения данных в основном определяются через «object»/«ref object». Конечно они не классы, но вполне себе объекты. При желании есть и наследование (единичное), и runtime dispatch (методы и мультиметоды). Правда всё-таки в основном предпочитается композиция и использование object variant'ов (что-то типа union структур в Си, но удобнее)
                  0
                  Я бы даже сказал: написал на питоне -> проверил, а тормозит ли оно вообще -> если надо, оптимизируй
              +4
              Таки хочется заметить — есть github.com/yglukhov/nimpy который позволяет использовать библиотеки Python'а в Nim, и наоборот — писать питоновские модули на Nim, всё через C API Python'а, обёрнуто в высокоуровневые макросы и типы.
              –14
              С примерами «всё плохо».

              21 век — тренд в сторону декларативного подхода даже не в «функциональных» языках, а у вас все примеры императивные.

              Приведённый первым хелловорлд на питоне можно уместить в одну строку
              print(*filter(lambda x: x[0] < 10 and x[0] * x[1] == 25, enumerate(nums)), )
              


              Что там в Ним с функциональным программированием?

              И с Фибоначчи, пример «плохого» алгоритма, у вас без ленивых вычислений, но с ограничениями и по рекурсии и по размерности результата.

                +3
                Что там в Ним с функциональным программированием?

                А что там с функциональным программированием у Python?

                  +7
                  Питон не заставляет, но позволяет писать в функциональном стиле:

                  • присутствуют функции высших порядков
                  • хочешь чистые функции — пиши чистые функции
                  • обработка списков от list comprehension до map-filter-reduce (см. функции высших порядков)
                  • рекурсия (а где её нет, но это тоже одна из концепций функционального программирования)
                  • ленивые вычисления
                  • замыкания, каррирование и т.д. и т.п.

                    +7

                    А почему примеры в этой статье должны быть обязательно в функциональном стиле? Чисто религиозные заморочки?


                    рекурсия

                    Без оптимизации хвостовой рекурсии


                    замыкания, каррирование и т.д. и т.п.

                    Где там "каррирование и т.д. и т.п." из коробки? Модуль functools что-ли с "reduce", "partial" и "lru_cache"?


                    Есть библиотека toolz там побольше всего. От фанатов для фанатов. С любовью.

                      0
                      > А почему примеры в этой статье должны быть обязательно в функциональном стиле?

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

                        +1
                        С примерами «всё плохо».

                        21 век — тренд в сторону декларативного подхода даже не в «функциональных» языках, а у вас все примеры императивные.

                        Вот это — это не вопросы, а явные утверждения. Причём похоже по форме на претензию. Да, писать так, чтобы исключить неверное толкование сложнее, чем может показаться :)

                          +1
                          Так это и не вопросы, а конкретная претензия к статье. Что не так?

                          Единственный вопрос, замечу, без подвоха заданный, «Что там в Ним с функциональным программированием?», остался без ответа.
                        0
                        > Где там «каррирование и т.д. и т.п.» из коробки?

                        Ну а как же
                        def func(arg1):
                            def _func(arg2):
                                ...
                            return _func
                        
                        func('arg1')('arg2')
                        


                        И вы ещё питоновские лямбды вспомните. Но они есть, уж как есть.
                          0

                          Это у вас результат преобразования, а где тут сама исходная функция func(arg1, arg2)?


                          Суть в том, чтобы получить из func(x, y) с помощью оператора каррирования func(x)(y). Такого из коробки нет.

                            –1
                            Как в сказке?

                            — Вы, чего, и конфеты за меня есть будете?
                            — Ага!
                              +3

                              Языки без каррирования тогда сложно назвать. Даже в С можно что-то изобразить, наверное.

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

                                  И что вы хотите этим сказать? Вы ставите знак равенства между частичным применением (фиксацией аргументов) и каррированием?


                                  Кстати, надо заметить, что functools.partial применима не всегда, что становится особенно актуальным с добавлением в язык positional only аргументов, доступных из python-кода. Зафиксировать можно либо позиционные аргументы слева-направо (leftmost), либо keyword-аргументы. Смотрите https://www.python.org/dev/peps/pep-0309/


                                  Попробуйте сделать функцию pow2 (x^2) с помощью functools.partial из функции pow.

                            +2
                            хочешь чистые функции — пиши чистые функции

                            А можно, чтобы тайпчекер это проверял? Да и вообще, можно мощный тайпчекер?

                              –3
                              Я так понимаю, что обсуждение чего угодно, если в нём упоминается питон, сводится к обсуждению питона? Да ну, надоело.

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

                                  На nim-lang.org очень всё хорошо и полно. И, главное, там нет сравнения с питоном )
                                    0
                                    Там немного вранье, АФАИК пока что Ним живет на ГЦ.
                                      0

                                      И не на одном, причём не все допилены.

                                        0
                                        refc годами работал и он очень стабилен, но уже долгое время вся работа идёт на ARC, и уже много прогресса — habr.com/ru/post/462577/#comment_21841990 для дополнительной информации :)
                                        0
                                        А где враньё на главной странице?) Там ничего не говорится про то, что у Nim'а нет GC.
                                          +2
                                          Nim's memory management is deterministic and customizable with destructors and move semantics, inspired by C++ and Rust. It is well-suited for embedded, hard-realtime systems.
                                          При использовании GC это ложь.

                                          Тем более, что тут же в оф.доке пишут честно про Soft realtime support
                                            +1
                                            Ну --gc:arc уже можно использовать на 1.2, а он как раз может использоваться в hard realtime :) Конечно да, ещё не полностью стабильно, но не так уж и много времени осталось до того, как этот пункт станет реальностью
                                              0
                                              Точно, только зарелизился первый патчсет 1.2.2
                                              --gc:arc. Plain reference counting with move semantic optimizations, offers a shared heap. It offers deterministic performance for hard realtime systems. Reference cycles cause memory leaks, beware.
                                              Ждем обещанную в релизнотах статью с подробностями.
                                  0

                                  Я даже перепроверил, что вы автор того же комментария, на который я отвечал. Зачем было все эти фичи тогда приводить?

                          +2
                          Вместо Nim→JS надо Python→Nim делать.
                            0
                            Так концептуально не получится — питон же не строго типизированный.
                            Например следующую функцию перенести в язык, где нету option/maybe (в каком-нибудь виде) невозможно по концептуальным причинам.
                            def f (x):
                                 if x > 2:
                                     return x
                                 else:
                                     return None
                            
                              0
                              Конкретно в этом случае можно вернуть объект, который будет обрабатываться вызывающей функцией с учетом того, какого типа значение будет возвращено внутри объекта. Но вообще, конечно, такого рода костыли могут сделать получившийся Nim-код нечитаемым.
                                0
                                питон же не строго типизированный
                                — строго, но динамически.
                                  0
                                  Согласен, переменные строго динамически типизированные.
                                  А вот функции — уже не строго типизированные (на чем и игра в этом примере).
                              +5
                              Это я дурак или код на первом же изображении — бессмыслица?
                              Картинка
                              image


                              И раз уж такая пляска, вот моя реализация (далеко не идеальная, конечно) чисел Фибоначчи на питоне:
                              Код
                              from time import time
                              
                              
                              def fibo(number):
                                  if number < 3:
                                      return 1
                                  num1 = 1
                                  num2 = 1
                                  num3 = num1 + num2
                                  counter = 3
                                  while counter < number:
                                      num1 = num2
                                      num2 = num3
                                      num3 = num1 + num2
                                      counter += 1
                                  return num3
                              
                              
                              start = time()
                              print(fibo(40))
                              print(time() - start)


                              Вывод
                              102334155
                              1.4066696166992188e-05 //секунд исполнялся код



                              Проводить бенчмарк на заведомо непроизводительной конструкции (рекурсия) — не очень показательно. Но даже в таком случае, поразительно, как 40-ое число могло считаться 37 (!) СЕКУНД. (у меня код из гитхаба проекта выполнялся 22 секунды на python3.7 AMD Ryzen 5 2600).

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

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

                                0
                                Из первого примера строчки 7 и 9 мне точно непонятны.
                                А то, echo — это вывод на экран(?), стало ясно только из текста.
                                  0
                                  5, 6 и 10 — несут еще меньше смысла.
                                  +1
                                  Может вместо `found = not found` надо написать `found = false`? А то на втором найденном совпадении оно снова станет false.
                                    0
                                    Формально, можно и так оставить: может быть не больше одного совпадения во внутреннем цикле, а после первого внутреннего цикла, изменившего значение, нас выкинет из внешнего.

                                    Что не отменяет того, что так писать, конечно, не стоит.
                                      +1
                                      может быть не больше одного совпадения во внутреннем цикле

                                      Это до тех пор, пока в коде валяются «магические цифры». Когда они вынесутся в константы и их начнут изменять — начнутся пляски с бубном.
                                        0
                                        Ну не совсем.

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

                                        Но да, если оставлять так, то нужно внимательно следить за условием (для произвольного условия good(num, i, 25) этот код не подходит).
                                      +1
                                      Этот код делает за O(n^2) то, что можно сделать за O(sqrt(n)), и с легкостью проваливается в бесконечный цикл на других входных данных. Фривольное обращение с булевыми переменными — не самая большая проблема.

                                      Суть тут скорее в том, чтобы показать показать синтаксис, чем больше разных операций — тем лучше.
                                      0
                                      У Nim почти 10000 звезд на GitHub
                                      почему-то мне кажется, что почти все звёзды оставили питонисты, наткнувшиеся на Nim в результате гуглёжки чего-то вроде «как ускорить Python», вздохнув после этого и вернувшись к Пайтону
                                        –3
                                        По этой ссылке — куча реализаций вычисления чисел Фибонначи на Python. Ни один из методов не тратит больше секунды на вычисление 40-го числа Фибоначчи (время выполнения колеблется в диапазоне от 3.193450927734375e-05 до 1.73965093640455e-02). Откуда у вас взялись целых 37 секунд не понятно. Так что ваш бенчмарк не вернен и Python в нем должен стоять на втором месте. Это касается именно вашего бенчмарка, уж не знаю насколько производителен Nim в других типах задач.
                                          0
                                          Справделивости ради — в статье есть ссылка на гитхаб с исходным кодом. Он выполняется действительно адски медленно. Но я с вами согласен, что задача поставлена неверно.
                                          +9
                                          Стоит ли переходить с Python на Nim ради производительности?

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


                                          /thread

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

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

                                            Проект живой, библиотеки есть и новые пишутся. Если чего-то нет, то не сильно сложно подтянуть код с/с++. Для меня он прочно занял нишу домашних проектов. Зачем вам взлёт нужен?
                                            +1
                                            Nim в связке с Godot весьма неплох.
                                            0

                                            Я вспомнил, что целую оду Nim написал один интересный человек, который, кстати, оставил эпичные комментарии к одной статье.

                                              +1
                                              Да, там такие оды Nim-у пелись, но на практике что-то у этого языка не получилось…
                                                0
                                                Да он то зарелизился только пару месяцев назад.

                                                А уже хороните =0)
                                                  0
                                                  Да он то зарелизился только пару месяцев назад.
                                                  Да, через 16 лет после своего первого появления на свет…
                                              0

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


                                              Но тащить на прод в результате не решился.


                                              Пайтон по скорости это конечно совсем ужас-ужас. Но повода переходить именно на это при наличии куда более жизнеспособных конкурентов (c#/go/Java) ни одного резона нет.


                                              Хотя язык очень приятный в работе, да

                                                0

                                                Не особо согласен что резонов прямо уж нету:


                                                c#/java не конкуренты — это гораздо более "энтерпрайз" решения — писать много и долго.
                                                go — писать меньше, но не всех устраивает язык, с набором слов как у Эллочки из !2 стульев

                                                  +1

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


                                                  Для крупняка таки ничего лучшее кровавого энтерпрайза не придумали.


                                                  А вот если нужно что-то одноразовомелкое — тут уже или набор слов не важен (хотя сам не представляю, как на этом можно излагать мысли), или скорость работы не важна, или скорость разработки не важна. Но обычно ничего вообще не важно, кроме наличия чужой библиотеки, решающей 90% задач и наличия в радиусе 100км еще хотя бы одного специалиста по этой технологии. А с этим у нима таки серьезные проблемы: за 5 лет дело с мертвой точки не сдвинулось.

                                                    0

                                                    Относительно библиотек, у Нима всё очень неплохо, учитывая его "популярность", так как биндинги к C пишутся очень просто. А есть ещё и биндинги к питон и JS, но я просто не интересовался.

                                                      +1

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

                                                    0

                                                    Кстати, в этом году обещают зарелизить Crystal 1.0 — пока про него никто в комментах не вспомнил.

                                                      0
                                                      Я интересовался. Показалось, что у него очень узкое позиционирование — только замена Руби в Вебе под Линух.
                                                        +1

                                                        Всё вообще не так.


                                                        Во-первых, руби это не только веб (ну да, проклятие Rails). Во-вторых, у Crystal синтаксис всё же отличается, поэтому gem'ы не скомпилятся без исправлений.


                                                        Зато у Crystal — всё лучшее от руби и при этом строгая типизация, компилируется в бинарник, по скорости сравнимо с C'шным кодом. Поддерживает и аналог goroutine (называется Fiber), и ядерные треды.


                                                        Фактически, везде, где Go применяется, там можно и Crystal применить.

                                                    +1
                                                    c#/java не конкуренты — это гораздо более «энтерпрайз» решения — писать много и долго.
                                                    Ну совсем необязательно так: и сами языки с возрастом приобретают в лаконичности и функциональности, и у обоих есть достаточно широкая экосистема, которая, что важно, поддерживается другими языками (и уж под JVM точно языки на любой вкус по лаконичности можно найти).
                                                  +3

                                                  Не смотря на сомнительное качество статьи, поделюсь положительным впечатлением от Нима;


                                                  Nim встретил не так давно: решил попробовать что за язык и за 2-3часа написал функционал сервис, который до этого писал на расте около трёх недель. При этом я писал async-await на Nim впервые. Ещё больше удивление вызвало, когда этот наколенный код ел cpu в два раза меньше.


                                                  Многие скептически говорят про ним: "ещё один нескучный язык, который не несёт никакой идеи", это не совсем так, Ним во главу угла ставит именно удобство программирования, при этом интегрируя в себя многие идеи из других языков: Синтаксис — питоно-подобный, немного от паскаля, статическая типизация, концепты, async-await, сейчас он пытается избавиться от GC, в языке есть разделение на чистые и нечистые функции. Да, целиком нельзя назвать что-то ключевой особенностью языка кроме цели — быть удобным, как и питон, однако, в отличие от питона это очень производительное, и при этом статически типизируемое, при этом эти особенности не добавляют много оверхеда при написании.


                                                  Кстати — метапрограммирование — очень сильная сторона Нима.

                                                    +1
                                                    > на расте около трёх недель

                                                    звучит круто. интересно, за счет чего?

                                                    > сейчас он пытается избавиться от GC

                                                    а зачем?

                                                    > в отличие от питона это очень производительное

                                                    вы часто упираетесь в производительность питона? любопытно, в каких задачах?
                                                      +2
                                                      вы часто упираетесь в производительность питона? любопытно, в каких задачах?

                                                      Да даже что-то тупое типа парсинга лога и подсчета статистики тех или иных событий. Знающий питон коллега за сходное (если не дольшее) писал код, который работал медленнее, чем то, что писал я не на питоне.


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

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

                                                          В коллекцию крылатых айти-выражений!

                                                        0

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

                                                          0
                                                          а зачем?

                                                          ради риалтайма


                                                          вы часто упираетесь в производительность питона? любопытно, в каких задачах?

                                                          да в любых. из головы:


                                                          • сериализация в json (что-то типа 3мб в секунду против 100+ у newtonsoft)
                                                          • дичайший оверхед по памяти
                                                          • невозможность толком распараллелить работу из-за GIL

                                                          Конечно же, если нужна просто числодробилка, для которой хватает numpy, то претензий нет вообще. Но при чем здесь python?


                                                          Вдобавок: сборка python'овых контейнеров — это что-то с чем-то. node_modules — это прямо дистрофик по сравнению с site_packages.

                                                            0
                                                            А можно уточнить.
                                                            — Ты сравнивал стандартную библиотеку питона с third-party фреймворком?
                                                            — Насколько все таки огромным был оверхед по памяти?
                                                            — Что за задачи такие, для которых не подходит multiprocessing?
                                                              +1
                                                              1. я все перепробовал (кроме ручной генерации по токенам/ записи в StringIO)
                                                              2. на моих задачах (сравнивал с тем же C#) 3-4 раза. 12гб вместо 3 — это больно.
                                                              3. плотно общающиеся друг с другом. Стандартный пример: несколько процессов пишут в очередь, много процессов очередь разгребают, один процесс собирает результаты в кучу. Тут или какие-нибудь IPC костыли, или никак.

                                                              P.S: я на питоне лет 8 "от звонка до звонка". язык люблю, знаю, все еще использую… иногда. Но производительность у него (за пределами нативных модулей) реально ужас-ужас.

                                                            0
                                                            1. В основном за счёт борров-чекера в асинке, не говоря о проблема, например в связке с монгой
                                                            2. Видимо хочется автоматического от, он и сейчас возможен, но надо прилагать руки
                                                            3. Часто, как только начинают появляться критические части в самом питоне, за пределами нампи, например
                                                              0
                                                              2 — я понял из первого комментария, что вы хотели бы ручное управление.

                                                              3 — понятно, я за пределы нампи не выходил). Как вариант, можно написать и подключить библиотеку на си/го на медленных участках.
                                                                0
                                                                Как вариант, можно написать и подключить библиотеку на си/го на медленных участках.

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

                                                              0
                                                              вы часто упираетесь в производительность питона? любопытно, в каких задачах?

                                                              Очень часто. В любой вычислителной задаче напрямую питон применять нельзя, провал по скорости будет в тысячи раз. При этом питон — самый популярный язык для анализа данных, машинного обучения и нейросетей. Он даже используется для вычислительного моделирования
                                                              Парадокс?
                                                              Дело в том, что чистый питон никто не использует для этих целей. Используются фреймворки написанные на C (+CUDA) или фортране, которые предоставляют интерфейсы для тензорных операций (numpy, cupy, для нейросетей Chainer, pyTorch, Tensorflow). Вычислительный алгоритм нужно переработать в набор тензорных операций, тогда производительность будет сравнима с решениями на C. Но истинная боль начинается если вы не можете какие-то части представить в виде тензорных операций…
                                                              Это напоминает строительство дома из панельных блоков — пока вы хотите что-то стандартное, все идет легко, сделать что-то нестандартное можно только при помощи хаков и ухищрений.

                                                              Создатели Julia собирались решить эту проблему, чтобы было возможно комбинировать прямое итерирование по циклам например с высокоуровненвыми тензорными операциями.
                                                                –5
                                                                И тут на арену победоносной поступью выбегает Cython.
                                                                  0
                                                                  Куча минусов. Cython настолько плох?
                                                                  В чём его проблемы?
                                                                    +2

                                                                    Cython это урезанный си с питоно-подобным синтаксисом. Т.е. такая полу-мера.
                                                                    Если ничего кроме питона не знаешь, и надо написать не много строчек, то сойдёт.
                                                                    Для чего-то более сложного лучше взять полноценный язык, с нормальной поддержкой ide и всеми фичами этого языка. А для полу-мер сейчас есть numba jit, которая как и cython требует аннотировать часть типов, но при это остается чистый синтаксис питона.

                                                                  +2
                                                                  Что мешало им сделать Джулию статически типизированной? И опционально добавить dynamic как в C# например, но нет — теперь есть ещё один динамически типизируемый язык с отсутствием возможности проверки типов на этапе компиляции. Что-то серьёзное и большое на динамически типизируемых языках писать — то ещё адище.
                                                                    0
                                                                    Скажу честно — не знаю, я тоже придерживаюсь мнения что статическая типизация лучше во всех отношениях, но к сожалению она непопулярна в сообществе анализа данных.
                                                                      0
                                                                      В общем соглашусь, идейно статическая типизация мне тоже нравится больше, хотя больших монолитных проектов не пишу. Было бы действительно приятно видеть в джулии больше проверок до запуска. Но такого идеального языка не нашлось, а по сравнению с питоном в плане проверок типов она явно выигрывает.
                                                                        0
                                                                        Так вроде Джулия и есть статически типизированная, с автовыводом типов?!
                                                                          +1

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


                                                                          https://stackoverflow.com/questions/28078089/is-julia-dynamically-typed

                                                                            0
                                                                            Я так понимаю ответ со стековерфлоу, что там, где типы известны (указаны или выведены), джулия сразу генерит машкод.
                                                                              0
                                                                              До первого вызова функции ничего не генерируется, даже если написать «f(x::Int) = 2 * x». Ну и такие случаи, когда явно указаны конкретные типы, редко встречаются на самом деле.
                                                                            0
                                                                            Типы проверяются на этапе компиляции — но функции компилируются при их первом вызове, так что фактически это почти то же самое, что время выполнения.
                                                                            0

                                                                            Так-то можно написать типы ко всем переменным, запаковать программу в main() и смотреть @code_warntype main() на предмет невыведенных типов.


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


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

                                                                        0
                                                                        Про производительность можете чуть пояснить? Вот в статье на графике нарисовано, что nim в 5 раз медленнее чем Си, даже на тривиальной задаче.
                                                                          0

                                                                          Я не особо хочу рассматривать цифры тестов из самой статьи, так как я писал, что качество под вопросом, но, по опыту, ним компилится в C, и, обычно, если надо очень сильно соптимизировать, то делается это уже смотря на C-шный код, соответственно оптимизация обычно заключается в поиске того где ним что-то сделал неоптимально, соответственно в 90% случаях что я видел или сама генерация из ним была +- на C уровне, или как минимум (при необходимости) оптимизировалась до C-шного варианта

                                                                            +1
                                                                            Как я уже написал в другом комментарии — в статье автор компилит и C, и Nim без оптимизации — если с полными оптимизациями, то скорость примерно равна.
                                                                              +2
                                                                              Мда, понятно. Как вообще можно было догадаться проводить бенчмарк без оптимизации…
                                                                            0

                                                                            Насчёт GC хотелось бы уточнить — конечно проводится много работы в сторону ARC/ORC, но дефолтным GC в будущем будем скорее всего ORC (ARC со сборщиком циклов). ARC сам по себе не является полноценным GC, это так. Просто немного неправильно звучит "избавиться от GC" :)

                                                                              0
                                                                              А как в стд-библиотеках, ну и в приложениях, производится разделение — под каким --gc мы работаем? Ведь логика должна, хоть по мелочи меняться. А я так понимаю, что все модули программы должны быть собраны под единый --gc?

                                                                              Так то GC — перерасход памяти, ARC — дорогой по сумме затрат, итп

                                                                                0

                                                                                Есть when defined (gcDestructors) — но это мало где нужно, в большинстве случаев никаких изменений не нужно, только если оптимизации именно для ARC (я про вещи типа sink/lent). И да, ARC — это automatic reference counting (ну и move semantics), это не atomic reference counting (некоторые путают с ARC в Swift)

                                                                            0

                                                                            С появлением LLVM расплодилось много языков. В чём фишка Nim я так и не понял.
                                                                            Есть rust — там есть бороучекер и забавная система трейтов.
                                                                            Есть Julia — там рекламируют какую-то полу динамическую типизацию с jit.
                                                                            Про остальные толком никто мне на хабре пока не объяснил :)

                                                                              +1

                                                                              Ним к llvm никаким боком.

                                                                              –1
                                                                              Фишка Нима в удобстве написания программ (контра расту), но не в ущерб производительности.
                                                                              Джулия — язык для математики, другой профиль.
                                                                                +1

                                                                                А в чём удобство? Не надо думать об управление памятью? Есть исключения? В чём отличие от java/c#?

                                                                                  0
                                                                                  Нативные бинарники, нативная производительность, не нужно никакого дополнительного рантайма/VM, всё портабельно (если под платформу есть Си компилятор — там вполне возможно использовать Nim).

                                                                                  А так есть все фичи для «удобства» написания программ — и автоматическое управление памятью, и исключения, и так далее, и всё быстро и удобно :)
                                                                                  +1
                                                                                  Джулия — язык для математики, другой профиль.

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

                                                                                    –1
                                                                                    Так в числодробилках типы и не нужны.

                                                                                    Про теоркат и типы спишу на выпендрёж — слишком далеко от реальности.
                                                                                    Заголовок спойлера
                                                                                    Это как теории секса
                                                                                      +3
                                                                                      Так в числодробилках типы и не нужны.

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


                                                                                      Про теоркат и типы спишу на выпендрёж — слишком далеко от реальности.

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

                                                                                        0

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


                                                                                        Важно знать что размерности векторов и матриц правильные, потому что эти операции часто довольно абстрактные. А вот конкретная формула из прикладной области — её не доказать правильная она или нет. Можно совершить опечатку при переписывании её с листика...


                                                                                        Да и вообще насколько хорошо можно что то доказывать про float числа? Выходят ли зав-типы за рамки дискретной математики?


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

                                                                                          +1
                                                                                          Важно знать что размерности векторов и матриц правильные, потому что эти операции часто довольно абстрактные. А вот конкретная формула из прикладной области — её не доказать правильная она или нет. Можно совершить опечатку при переписывании её с листика...

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


                                                                                          Да и вообще насколько хорошо можно что то доказывать про float числа?

                                                                                          Есть какие-то формализмы, но я про них мало знаю.


                                                                                          Выходят ли зав-типы за рамки дискретной математики?

                                                                                          Флоаты-то тоже дискретные :)


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

                                                                                          У меня были мысли на эту тему, но я посмотрел на количество интринсиков и особенности их применения, прикинул соотношение фан/рутина (получилось, что оно стремится к нулю) и забил.

                                                                                      –1

                                                                                      Фунты и килограммы это какой то фронтенд. В симуляциях должны быть безразмерные величины.

                                                                                        +1

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

                                                                                        0
                                                                                        При попытке сложить несовместимые единицы измерения (сюда же величина + производная) — ошибка; если индекс массива по недосмотру стал отрицательным — ошибка; должно быть целое число, а как-то пробралось нецелое — ошибка; если функция принимает массив и скаляр одного типа, а переданы разные/несовместимые — ошибка; и т.п.
                                                                                        Да, упомянутые ошибки выдаются не до начала выполнения программы, а во время компиляции конкретной функции, или при её выполнении если зависят от конкретных значений. Но случайно проглотить их таки не получится.

                                                                                        Кстати, в математику ещё статистика/анализ данных входит — это тоже очень широко в джулии представлено.

                                                                                        А что там кодят в теоркате было бы интересно почитать на простом уровне :) Статью не планируете написать?
                                                                                          +1
                                                                                          Да, упомянутые ошибки выдаются не до начала выполнения программы, а во время компиляции конкретной функции

                                                                                          Это в смысле JIT'а? То есть, там либо остаются проверки на случай деоптимизации для других типов, либо там как-то что-то доказывается, что других типов там не может быть?


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

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


                                                                                          А что там кодят в теоркате было бы интересно почитать на простом уровне :) Статью не планируете написать?

                                                                                          Да там особо нечего писать. Можно доказывать, что ваши монады на самом деле монады (и мне это несколько раз, когда я писал свои монады, было бы полезно). Ну или можно просто преподавать теоркат — не зря там ML выбрали, а не лишп какой-нибудь.

                                                                                            0
                                                                                            Это в смысле JIT'а? То есть, там либо остаются проверки на случай деоптимизации для других типов, либо там как-то что-то доказывается, что других типов там не может быть?

                                                                                            Для каждого места вызова каждой функции в коде компилятор выводит, какой тип она может вернуть. Это может быть Float64, Number, Union{Float64, Nothing}, Union{Int, Vector{Int}, Nothing}, Any,…. Потом компилятор переходит к следующему выражению, и так далее. Этот вывод всегда верный в том смысле, что вызов функции заведомо вернёт значение выведенного типа. Поэтому проверки после компиляции остаются если тип не конкретный: если выведено, что какое-то значение типа Union{Int, Nothing}, затем оно передаётся в другую функцию, то при выборе какой вариант этой функции вызывать (для Int или для Nothing) будет стоят if. Если выведено Any, то при его передаче куда-то будет динамический диспатч, и т.п. Можно вручную типы значений указывать — тогда будет во-первых дополнительная проверка (при компиляции или при выполнении — зависит от), а во-вторых после этого не будет динамического диспатча. Но вызываемая функция в любом случае всегда компилируется для конкретного типа.

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

                                                                                            Да, с этим я не спорю конечно.
                                                                                              0

                                                                                              Звучит более-менее как обычный вывод типов, возможно, со всякими там path sensitivity. Тогда что мешает делать это при написании кода?

                                                                                                0
                                                                                                Насколько я понимаю, одна из причин в том, что никак не вывести, например, тип значений считанного файла с данными — компилятор скажет, что тип это Dict{String, Any} в случае чтения json'а. Конкретные типы каждого поля известны только в момент выполнения, поэтому и про результат применения к ним какой-нибудь функции f(data[«field»]) компилятор заранее ничего не знает. А почти любая программа и начинается с чтения файлов данных/конфигов.

                                                                                                Для библиотечного кода — про тип, возвращаемый даже простейшей функцией «f(a::T, b::T) = a + b», ничего нельзя сказать пока неизвестен T.

                                                                                                С сильно статически типизированными языками я особо не работал — только «игрушечные» вещи писал — не знаю как там обычно эти проблемы решаются.
                                                                                                  0
                                                                                                  Насколько я понимаю, одна из причин в том, что никак не вывести, например, тип значений считанного файла с данными — компилятор скажет, что тип это Dict{String, Any} в случае чтения json'а.

                                                                                                  Ничего страшного, это ровно то, что я ожидаю от функции чтения жсона. Ну, почти ­— словарь является частным случаем, соответствующим Object по ссылке.


                                                                                                  Конкретные типы каждого поля известны только в момент выполнения, поэтому и про результат применения к ним какой-нибудь функции f(data[«field»]) компилятор заранее ничего не знает. А почти любая программа и начинается с чтения файлов данных/конфигов.

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


                                                                                                  Для библиотечного кода — про тип, возвращаемый даже простейшей функцией «f(a::T, b::T) = a + b», ничего нельзя сказать пока неизвестен T.

                                                                                                  А каков тип +?


                                                                                                  Даже если это множественный диспатч в стиле ad-hoc polymorphism (умное название для перегрузки функций) — рано или поздно в точке вызова функции её конкретный тип станет известен.

                                                                                                    +1
                                                                                                    Если хочется писать в духе
                                                                                                    data = JSON.read("file.json")
                                                                                                    mean(data["field"])
                                                                                                    

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

                                                                                                    А каков тип +?

                                                                                                    Зависит от типа аргументов — я могу сделать свой тип MyT и оператор MyT + MyT -> MyOtherT. Или даже возвращать значения разных типов в зависимости от значения: "+(x::MyT, y::MyT) = x == y? 0: 'a'". Для оператора + это выглядит странно, но с другими функциями часто полезно.

                                                                                                    Даже если это множественный диспатч в стиле ad-hoc polymorphism (умное название для перегрузки функций) — рано или поздно в точке вызова функции её конкретный тип станет известен.

                                                                                                    В точке вызова в момент вызова — да, конечно, функция именно тогда и компилируется. В точке вызова в момент компиляции — нет, см. пример с чтением файла.
                                                                                                      0
                                                                                                      Если хочется писать в духе
                                                                                                      data = JSON.read("file.json")
                                                                                                      mean(data["field"])


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

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

                                                                                                        0
                                                                                                        У вас последняя ссылка (на пример) не работает.
                                                                                                        0
                                                                                                        Так ваш пример кода никак не противоречит моему описанию:
                                                                                                        И тут уж выбор создателей языка — либо мы перед вызовом mean требуем явно привести к конкретному типу, либо оставляем динамическую типизацию.

                                                                                                        А именно, у вас явным образом в коде задаётся конкретный тип data.field:
                                                                                                        struct Data {
                                                                                                            field: Vec<f64>,

                                                                                                        Если брать чтение не из json, а из более богатых форматов, то там вместо вектора float64 может быть, к примеру, Set{BigFloat}, или весь объект data окажется ленивым DataFrame, где колонка field это что-то типа LazyVector{Int}. При этом 'mean(read(«data_file»).field)' продолжит работать — если тип файла поддерживается, конечно.

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

                                                                                                        Кстати, mean из вашего кода работает только для массивов float64, как я понял — но здесь не уверен, приведено ли это просто для примера, или система типов rust'а ограничивает общность.
                                                                                                          0
                                                                                                          Если брать чтение не из json, а из более богатых форматов, то там вместо вектора float64 может быть, к примеру, Set{BigFloat}, или весь объект data окажется ленивым DataFrame, где колонка field это что-то типа LazyVector{Int}. При этом 'mean(read(«data_file»).field)' продолжит работать — если тип файла поддерживается, конечно.

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


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

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


                                                                                                          Кстати, mean из вашего кода работает только для массивов float64, как я понял — но здесь не уверен, приведено ли это просто для примера, или система типов rust'а ограничивает общность.

                                                                                                          Просто для примера, да, на Rust можно написать и обобщённую версию.

                                                                                                            +1
                                                                                                            Функция mean ожидает, что переданный ей аргумент — это нечто итерируемое, что выдаёт значения, которые можно складывать и делить

                                                                                                            Да, это почти так, в неплохом приближении.

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

                                                                                                            Ну вот по вашему примеру не понятно, как тут помогает статическая типизация.

                                                                                                            Можно сделать так, чтобы каждая функция явно накладывала ограничения типа «хочу что-то, что итерирует значения, которые можно складывать и умножать на число». Но языков, где это удобно сделано, я не видел (раст не знаю) — видимо, не всё так просто. При этом ведь ещё и перегрузка по этим признакам нужна: если дали плотный массив, а не просто итератор, то будем использовать simd.

                                                                                                            Возможно, сам по себе факт статической типизации действительно позволяет не накладывать лишних ограничений на структуры данных. Но по факту тот код, что я видел в языках со статической типизацией, добавляет кучу ограничений. Вот кто-то, как вы в примере, пишет небольшую функцию mean — она принимает массив float64, и ничего больше туда не передать. Для получения общности нужно специально заморачиваться, скорее всего писать больше кода. В динамике же наоборот: по-умолчанию функция, определённая как 'mean(x) = sum(x) / length(x)' будет работать для всех значений, которые умеют считать свою сумму и длину. Большая часть функций пишется для конкретной цели, и в статике они скорее всего будут принимать или конкретный тип, или недостаточно общий шаблонный. Соответственно, в другой ситуации их использовать затруднительно/неоптимально.

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

                                                                                                            По мере роста кодовой базы разница становится несущественной.

                                                                                                            Вероятно, разница действительно уменьшается с ростом размера проекта. Но такие мелкие вещи — взять файл, посмотреть на его часть, идти дальше — часто встречаются в интерактивной работе, и очень не хочется каждый раз эти все структуры писать.
                                                                                                            Как пример, который менее «вырожден» по сравнению с mean:
                                                                                                            data = JSON.read("data.json")
                                                                                                            plt.scatter(data.field_1, data.field_2)
                                                                                                            

                                                                                                            нарисует график с точками field_1 vs field_2 независимо от того, числа там или строки.
                                                                                                              +1
                                                                                                              Можно сделать так, чтобы каждая функция явно накладывала ограничения типа «хочу что-то, что итерирует значения, которые можно складывать и умножать на число». Но языков, где это удобно сделано, я не видел (раст не знаю) — видимо, не всё так просто.

                                                                                                              Посмотрите на Haskell.


                                                                                                              При этом ведь ещё и перегрузка по этим признакам нужна: если дали плотный массив, а не просто итератор, то будем использовать simd.

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


                                                                                                              В динамике же наоборот: по-умолчанию функция, определённая как 'mean(x) = sum(x) / length(x)' будет работать для всех значений, которые умеют считать свою сумму и длину.

                                                                                                              Отнюдь, тут неявных предположений куда больше. Помимо того, что x — это что-то, с чем умеют работать sum и length, тут ещё есть неявное предположение, что на значениях, возвращаемых sum и length, определена операция деления, и что sum и length не меняют свои аргументы (так что если x является потоком данных из файла, то mean правильно работать не будет). А, ещё и то, что "типы" возвращаемых значений от вызова к вызову не меняются и не зависят от конкретных значений аргументов.


                                                                                                              По мере роста кодовой базы разница становится несущественной.

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

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


                                                                                                              Как пример, который менее «вырожден» по сравнению с mean:
                                                                                                              data = JSON.read("data.json")
                                                                                                              plt.scatter(data.field_1, data.field_2)


                                                                                                              нарисует график с точками field_1 vs field_2 независимо от того, числа там или строки.

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

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

                                                                                                                Какую замену нашли?

                                                                                                                  0
                                                                                                                  Посмотрите на Haskell.

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

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

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

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

                                                                                                                  … 'mean(x) = sum(x) / length(x)'...

                                                                                                                  Отнюдь, тут неявных предположений куда больше.

                                                                                                                  Неявных предположений действительно больше, но зато автоматом будет работать для кучи типов контейнеров. А предположений вообще явно меньше, чем в вашем варианте. Я же здесь сравниваю простейшую реализацию mean в вашем примере по ссылке (вероятно, многие программисты на условном rust напишут именно такой код, если им нужно находить среднее значение массивов из float64), и простейшую реализацию на julia как примере динамически типизированного языка (большинство людей напишут примерно такую реализацию, если им скажут что нужно усреднить массив float'ов). И здесь по количеству предположений на входные данные явно выигрывает второй вариант.

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

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

                                                                                                                  Вы так говорите «обрушится», как будто выдаст сегфолт как в С :) В большинстве случаев будут относительно разумные exception, типа размер не совпадает, или подали в качестве значений непонятную зверушку. Да, матплотлиб действительно не эталон идеального API и ошибок, но тут дело не в динамичности языка, а то что они изначально решили чересчур аггрессивно угадывать, что хотел пользователь. Если бы не это, то можно было бы выдавать более адекватные ошибки типов сразу при попытке вызова функции. Но если выбирать между текущим поведением и необходимость каждый раз вместо
                                                                                                                  data = JSON.read("data.json")
                                                                                                                  plt.scatter(data["field_1"], data["field_2"])

                                                                                                                  писать что-то типа
                                                                                                                  data = JSON.read("data.json")
                                                                                                                  plt.scatter(data.as_float_array("field_1").unwrap(), data.field_2.as_int_array("field_1").unwrap())

                                                                                                                  то я выбираю нынешний вариант.
                                                                                                                  Ну и матплотлиб написан на питоне, в котором множество недостатков, не вызванных напрямую динамической типизацией. Пользуюсь просто потому, что в нём можно быть уверенным, что в итоге график будет выглядеть ровно как задумывалось. Подстройка даже редких параметров отображения легко гуглится в силу популярности — с другими библиотеками нужного результата не всегда получалось добиться.
                                                                                                                    +1
                                                                                                                    Ну и оптимизации на компилятор перекладывать можно только относительно очень сильно ограниченных, преимущественно локальных, преобразований. Какой-нибудь mean к simd'у наверное соптимизируется, а вот наивное перемножение матриц явно недотянет до скорости blas'а независимо от компилятора.

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


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

                                                                                                                    Опять-таки, это лишь пример. Ничто не мешает мне написать что-то вроде


                                                                                                                    fn mean<I>(values: I) -> f64
                                                                                                                    where
                                                                                                                        I: IntoIterator<Item = f64>,
                                                                                                                        I::IntoIter: ExactSizeIterator,
                                                                                                                    {
                                                                                                                        let values = values.into_iter();
                                                                                                                        let len = values.len();
                                                                                                                        values.sum::<f64>() / len as f64
                                                                                                                    }

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


                                                                                                                    Вы так говорите «обрушится», как будто выдаст сегфолт как в С :) В большинстве случаев будут относительно разумные exception, типа размер не совпадает, или подали в качестве значений непонятную зверушку.

                                                                                                                    Или не будет. У меня вот знакомый жаловался на бажный код:


                                                                                                                            if inputs.get_shape().ndims == 2:
                                                                                                                                result = tf.matmul(inputs, weight)
                                                                                                                            else:
                                                                                                                                reshaped_inputs = tf.reshape(inputs, [-1, input_dim])
                                                                                                                                result = tf.matmul(reshaped_inputs, weight)
                                                                                                                                result = tf.reshape(result, tf.pack(tf.unpack(tf.shape(inputs))[:-1] + [output_dim]))
                                                                                                                    
                                                                                                                            if biases:
                                                                                                                                result = tf.nn.bias_add(
                                                                                                                                    result,
                                                                                                                                    lib.param(
                                                                                                                                        name + '.b',
                                                                                                                                        np.zeros((output_dim,), dtype='float32')
                                                                                                                                    )
                                                                                                                                )
                                                                                                                            return result

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


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


                                                                                                                    Но если выбирать между текущим поведением и необходимость каждый раз вместо
                                                                                                                    data = JSON.read("data.json")
                                                                                                                    plt.scatter(data["field_1"], data["field_2"])


                                                                                                                    писать что-то типа
                                                                                                                    data = JSON.read("data.json")
                                                                                                                    plt.scatter(data.as_float_array("field_1").unwrap(), data.field_2.as_int_array("field_1").unwrap())


                                                                                                                    то я выбираю нынешний вариант.

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

                                                                                                                      0
                                                                                                                      Ничто не мешает мне написать что-то вроде <...> — примерно тот же код, что и для вашего варианта.

                                                                                                                      Серьёзно, «примерно тот же код» :)? Здесь явно требуется бОльшее знание раста, чем в изначальной вашей реализации, которую я сразу понял. При этом ограничение на float осталось.
                                                                                                                      Ну и в расте нет (по крайней мере раньше не было) перегрузок функций. То есть эффективные методы а-ля mean(Multiset), mean(AggregateStatistics), mean(ArrayWithCachedSum), mean(RandomDistribution) не добавить — а ведь из таких вещей и складывается переиспользование/расширяемость библиотек.

                                                                                                                      Глобально у меня такое ощущение, что вы во многом путаете свойства динамической типизации и характеристики её конкретной реализации (питона). Это аналогично тому, что мы возьмём джаву (no offense) и будем считать её преимущества/недостатки присущими статический типизации вообще.
                                                                                                                      Пара примеров этого:
                                                                                                                      Во-вторых, конкретно это я бы оставил на откуп Достаточно Умного(TM) компилятора, тем более что с типами такие оптимизации проводить куда проще.

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

                                                                                                                      Производительность и статичность/динамичность типизации особо не связаны друг с другом. То, что конкретные типы аргументов аргументов функции становятся известны только непосредственно перед её запуском, никак не мешает её скомпилировать с нужными оптимизациями в это время. При этом (после компиляции всех нужных вариантов функций) оверхед абсолютно нулевой: исполняется такой же машинный код, что был бы при компиляции «заранее».

                                                                                                                      В большинстве случаев будут относительно разумные exception, ...

                                                                                                                      Или не будет.

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

                                                                                                                      Так если никакого эксепшена не выдалось, значит не было проверки типов вообще, ни статической, ни динамической. С массивами во всяких numpy/theano/… достаточно много всяких попыток угадать, что же хочет пользователь — мне тоже не очень нравится, но это сознательное решение создателей библиотеки и типизация тут ни при чём. Уверен, что на условном C++ с шаблонами (и на более богатых языках) тоже можно написать библиотеку массивов так, что функции будут принимать массивы почти любого размера и пытаться согласовать их друг с другом. В таком случае никаких ошибок не будет и окажется тот же неожиданный результат.

                                                                                                                      Вообще же я абсолютно не против проверки типов до запуска — при прочих равных с ней бесспорно лучше. Конкретно в джулии, например, можно реализовать некую промежуточную опциональную проверку типов: когда в какую-то функцию подаётся аргумент конкретного типа, пусть проверяется и компилируется сразу и эта функция, и все вызываемые из неё. Если нашлось место, где не согласуются типы или вызывается несуществующий метод — сразу давать ошибку. Если типы не получилось полностью вывести и где-то оказался «Any» — выдавать ворнинг. Основная часть этой машинерии уже есть, ведь компилятор и так делает ровно эти действия, просто не сразу для всех вызываемых далее функций. Возможно, когда-нибудь и реализуют такое — но я отлично понимаю авторов, у которых это не в приоритете.
                                                                                            +1
                                                                                            Числодробилки? Ну, мне хочется типами гарантировать, что мои вычисления имеют смысл, и я не складываю случайно килограммы с фунтами, и тут, судя по тому, что я видел, джулия тоже не в топе.

                                                                                            Unitful.jl — и складывайте килограммы хоть с фунтами, хоть с каратами, лишь бы не с джоулями.


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

                                                                                              0
                                                                                              В числодробилках с плавающей точкой обычно другое волнует, в первую очередь — что задача в ходе преобразований не потеряет устойчивость или обусловленность.

                                                                                              Я напарывался на случаи, когда «ой, тут матрица была транспонированная, надо было по строкам идти, чтобы получить значения одной фичи», равно как и на «чёрт, PCA забыл, вот бы было круто это в типах выразить…»


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

                                                                                              Рад, что вы про это упомянули!


                                                                                              Есть, например, такое. Или есть такое — вот прям ровно для анализа неопределённостей.

                                                                                                0
                                                                                                Есть, например, такое. Или есть такое

                                                                                                Т.е. это какие-то символьные вычисления на хаскеле? В которые потом подставляются числа?

                                                                                                  +1
                                                                                                  Если правильно понял, то ваши ссылки ведут на всякий автодифф и на uncertainties — это вроде никак не связано с переносом действительной математики на float.
                                                                                                  Кстати, в джулии из такого есть: развитые пакеты для точной интервальной арифметики, включая гарантированный поиск корней/экстремумов; целая экосистема автоматического дифференцирования; ну и разные варианты uncertainty propagation тоже, конечно. Причём это всё добро применимо к более-менее любому джулия-коду.
                                                                                                    0
                                                                                                    Я напарывался на случаи, когда «ой, тут матрица была транспонированная, надо было по строкам идти, чтобы получить значения одной фичи», равно как и на «чёрт, PCA забыл, вот бы было круто это в типах выразить…»

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


                                                                                                    Есть, например, такое. Или есть такое — вот прям ровно для анализа неопределённостей.

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


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

                                                                                            0
                                                                                            А по сравнению с Cpython он как? Тот хоть частично с библиотеками дружит.
                                                                                            Язык без библиотек, имхо, мертв.
                                                                                              +1

                                                                                              Вот с этим кстати всё прекрасно. Дружит с любой C-библиотекой буквально после пары телодвижений.


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

                                                                                                0
                                                                                                Для Питоновских модулей часто используется Cython – странная, но мощная смесь Си и Питона, что позволяет ускорять численные вычисления в десятки и сотни раз. Используется во многих популярных библиотеках включая scikit-learn, SciPy, Pandas, etc. Также позволяет легко подключать библиотеки на Си/С++.
                                                                                                  +2

                                                                                                  Ну вот nim в этом отношении — это cython на стероидах. С метапрограммированием, блекджеком и шлюхами.


                                                                                                  https://github.com/yglukhov/nimpy

                                                                                              0
                                                                                              если версию на питоне обернуть в @numba.jit, работает сравнимо с Nim, ускорение в 15 раз.
                                                                                                +2
                                                                                                А если версию на nim собирать с флагом -d:release, то у меня получается ускорение в 6 раз. Не совсем понятно, почему автор для бэнчмарка использует debug версию.

                                                                                                А с --gc:arc ускорение в 9 раз, время тоже что и у си версии.
                                                                                                  0
                                                                                                  Ещё есть -d:danger для выключения всех рантайм проверок (таким был -d:release до 0.20, сейчас этот define безопаснее) :)
                                                                                                    0
                                                                                                    nim c -d:release -d:danger --gc:arc fib.nim
                                                                                                    real    0m0.091s
                                                                                                    user    0m0.089s
                                                                                                    sys     0m0.002s
                                                                                                    

                                                                                                    gcc -O3 fib.c
                                                                                                    real    0m0.447s
                                                                                                    user    0m0.446s
                                                                                                    sys     0m0.001s
                                                                                                    

                                                                                                    Подозрительно как-то.
                                                                                                      0
                                                                                                      Просто говорит о том, что возможно С компилятор скомпиленный Си код Nim'а понял лучше, чем чистый Си :)
                                                                                                        0

                                                                                                        JFI: достаточно -d:danger без release

                                                                                                  +1
                                                                                                  Nim чувствителен только к регистру первого символа.

                                                                                                  То есть, HelloWorld и helloWorld он различает, а helloWorld, helloworld и hello_world — нет.


                                                                                                  Неожиданный подход, интересно зачем?
                                                                                                    0

                                                                                                    Это описано в оффициально документации: для первого варианта удобно различать имя типа и переменной, когда как во втором это имя переменных, просто в разных case

                                                                                                      0

                                                                                                      Позволяет использовать библиотеку в snake_case в camelCase проекте сохраняя стиль написания даже для библиотечных символов. Да, фича довольно необычная, но почему нет?

                                                                                                      +1
                                                                                                      Вот правда, мотивация у языка вроде бы хорошая, но конкретной цели не видно – в чём тогда его смысл? Для сравнения по скорости с Си и типизации при условии сохранения всех фич и библиотек Питона есть Cython, странная, но офигенная вещь. Кому нужны современный синтаксис и скорость, без привязки к либам Питона, могут смотреть в сторону Rust или Go в зависимости от специфики задачи. В общем, Nim – проект любопытный, но не более, универсалом ему уже не быть, а в каждой отдельной теме есть проекты куда мощнее.
                                                                                                        0

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

                                                                                                        +1

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


                                                                                                        Макросы и метапрограммирование в Nim — одна из очень сильных сторон языка. Увы, в статье про это одно упоминание вскользь и абсолютно ненаглядный пример.


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

                                                                                                          +2
                                                                                                          Ради интереса написал код, который считает количество повторений каждого слова
                                                                                                          nim
                                                                                                          import tables, os, strutils
                                                                                                          
                                                                                                          var words = initCountTable[string]()
                                                                                                          
                                                                                                          for word in readFile(paramStr(1)).split():
                                                                                                              words.inc(word)
                                                                                                          
                                                                                                          echo words

                                                                                                          python
                                                                                                          from collections import Counter
                                                                                                          import sys
                                                                                                          
                                                                                                          words = Counter()
                                                                                                          
                                                                                                          with open(sys.argv[1],'r') as f:
                                                                                                              for line in f:
                                                                                                                  words.update(line.split())
                                                                                                          
                                                                                                          print(words)   


                                                                                                          Средний результат на гигабайтном файле с русской художественной литературой:
                                                                                                               NIM               NIM(GC:ARC)           PYTHON
                                                                                                          real    0m20.575s    real    0m16.782s    real    0m37.684s
                                                                                                          user    0m17.600s    user    0m13.735s    user    0m34.921s
                                                                                                          sys     0m1.063s     sys     0m1.083s     sys     0m0.967s
                                                                                                          


                                                                                                          Стандартная утилита wc:
                                                                                                          real    0m16.413s
                                                                                                          user    0m16.273s
                                                                                                          sys     0m0.114s
                                                                                                          

                                                                                                          Хотя она просто считает кол-во слов, а не ведёт подсчёт каждого. Но время почему-то одинаковое.

                                                                                                          UPD: Выше для nim посоветовали -d:danger
                                                                                                          real    0m12.107s
                                                                                                          user    0m11.518s
                                                                                                          sys     0m0.555s
                                                                                                          
                                                                                                          0
                                                                                                          А чем Nim лучше Julia? Последняя, насколько я знаю, тоже позиционируется как «удобство интерпретируемых языков + скорость компилируемых», да и вроде там даже есть поддержка Питоновских библиотек. Хотелось бы увидеть сравнение Джулии и Нима в плане скорости, если последний не намного быстрее, то возникает вопрос — почему должны переходить на него вместо Джулии? А ещё есть штуки типа Cython.
                                                                                                            +1
                                                                                                            Если честно — оригинальная статья очень плохая, просто идёт описание основного синтаксиса и т.д. Автор даже бенчмарк не смог нормально провести (!) — результаты Nim тут в полном отладочном режиме.
                                                                                                            См. github.com/yakkomajuri/fibonacci-benchmark/issues/1 насчёт бенчмарка и forum.nim-lang.org/t/6577 для мнения от другого человека :)
                                                                                                              0
                                                                                                              Почему то забыли про Cython? Простота Python, те же фреймворки и библиотеки, высокая скорость. Зачем NIM?
                                                                                                                0
                                                                                                                Потому что Цитон оказался на практике неудобным и малораспространенным. Почему?

                                                                                                                Можно еще PyPy вспомнить.
                                                                                                                On average, PyPy is 4.4 times faster than CPython
                                                                                                                0
                                                                                                                Смущает, что двоеточие используется в 3 случаях:
                                                                                                                1) определение типа
                                                                                                                2) определение блока
                                                                                                                3) установление значений
                                                                                                                  +1

                                                                                                                  А это не смущает?


                                                                                                                  Nim чувствителен только к регистру первого символа.
                                                                                                                  То есть, HelloWorld и helloWorld он различает, а helloWorld, helloworld и hello_world — нет.

                                                                                                                  Зачем так делать вообще?

                                                                                                                    +2
                                                                                                                    Вот и я про то же, по ощущениям, в Nim синтаксис непроработанный, автору вот захотелось так — он так и сделал… Многие такие небольшие проекты языков программирования одного человека страдают тем, что фичи/синтаксис добавляются от балды как автору «нраицца», без каких-либо видимых причин на то. По крайней мере, так выглядит со стороны.
                                                                                                                      –1
                                                                                                                      Если ты не можешь придумать этой фиче применение — это не проблема языка.
                                                                                                                        +2

                                                                                                                        Предложите разумные варианты применения этой фичи.


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

                                                                                                                          –2
                                                                                                                          TDataBinding, CString,…
                                                                                                                            +1

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


                                                                                                                            Мне когда-то приходилось иметь дело с кодом, в котором происходило преобразование полей вида fooBar в foo_bar, но всё это делалось на уровне ключей словарей или при динамическом построении объектов, для согласования API, создания фасадов и т. п.


                                                                                                                            Я представляю, как бы дико выглядел кейс, в котором есть некая библиотека, у которой сущности именуются в lowerCamelCase, а кто-то у себя в коде работает с ней через snake_case, потому что ему так удобнее, потому что code style и потому что язык это позволяет, так почему бы и нет чёрт возьми. :)

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

                                                                                                                    Эта статья (оригинал) довольно низкого качества. Плохие бенчмарки, никакого углубления в тему.


                                                                                                                    Вот эта статья, на мой взгляд, даёт гораздо более полное представление о том, что такое Nim.