Привет, Хабр!


Уже довольно давно я пишу свой игровой фреймворк — такой pet project для души. А так как для души нужно выбирать что-то, что нравится (а в данном случае — на чём нравится писать), то выбор мой пал на nim. В этой статье я хочу поговорить именно про nim, про его особенности, плюсы и минусы, а тема геймдева лишь задаёт контекст моего опыта — какие задачи я решал, какие трудности возникли.


Давным-давно, когда трава была зеленее, а небо чище, я встретил nim. Хотя нет, не так. Давным-давно я хотел заниматься разработкой игр, чтобы написать свою Самую Классную Игру — думаю, многие проходили через это. В те времена Unity и Unreal Engine только-только стали появляться на слуху и, вроде как, ещё не были бесплатными. Я не стал их использовать, не столько из-за жадности, сколько из-за желания написать всё самому, создать игровой мир полность с нуля, с самого первого нулевого байта. Да, долго, да, сложно, зато сам процесс приносит удовольствие — а что ещё для счастья надо?


Вооружившись Страуструпом и Qt, я хлебнул говна по самое небалуй, потому что, во-первых, не был одним из 10 человек в мире, знающих C++ хорошо, а, во-вторых, плюсы активно вставляли мне палки в колёса. Не вижу смысла повторять то, что за меня уже замечательно написал platoff:


Как я нашел лучший в мире язык программирования. Часть 1
Как я нашел лучший в мире язык программирования. Часть 2
Как я нашел лучший в мире язык программирования. Часть Йо (2.72)


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

Работая с C++, я постоянно думал, как мне написать то, что я хочу, а не что мне написать. Поэтому я перешёл на nim. С историей покончено, давайте же я поделюсь с вами опытом после нескольких лет работы на nim.


Общие сведения для тех, кто не в курсе


  • Компилятор открытый (MIT), разрабатывается энтузиастами. Создатель языка — Andreas Rumpf (Araq). Второй разработчик — Dominik Picheta (dom96), написавший книгу Nim in action. Также некоторое время назад компания Status стала спонсировать разработку языка, благодаря чему у nim'а появились ещё 2 фуллтайм-разработчика. Помимо них, разумеется, контрибутят и другие люди.
  • Недавно вышла версия 1.0, а это значит, что язык стабилен и "breaking changes" больше не ожидаются. Если раньше вы не хотели использовать unstable версию, потому что обновления могли сломать приложение, то теперь самое время попробовать nim в своих проектах.
  • Nim компилируется (или транспилируется) в C, C++ (которые далее компилируются в нативный код) или JS (с некоторыми ограничениями). Соотвественно, при помощи FFI вам доступны все существующие библиотеки для C и C++. Если нет нужного пакета на nim — поищите на си или плюсах.
  • Ближайшие языки — python (по синтаксису, на первый взгляд) и D (по функционалу) — имхо

Документация


Вообще-то с этим плохо. Проблемы:


  1. Документация раскидана по разным источникам
  2. Документация гавно не в полной мере описывает все возможности языка
  3. Документация порой слишком лаконична

Пример: хотите вы написать многопоточное приложения, ядер-то много, а девать некуда.
Вот раздел официальной документации про потоки. Нет, понимаете, потоки — это отдельная большая часть языка, его фича, которую даже нужно включать флагом --threads:on при компиляции. Там shared или thread-local heap в зависимости от сборщика мусора, всякие shared memory и locks, thread safety, специальные shared-модули и хрен знает что ещё. Откуда я про это всё узнал? Правильно, из книги nim in action, форума, stack overflow, телевизора и от соседа, в общем откуда угодно, но не из официальной документации.


Или вот есть т.н. "do notation" — очень хорошо заходит при использовании шаблонов и тд, вообще везде где надо передать callback или просто блок кода. Где про это можно почитать? Ага, в мануале по экспериметальным фичам.


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


На форуме и в github issues проскакивали предложения по улучшению документации, но дело так и не сдвинулось. Мне кажется, не хватает какой-то жёсткой руки, которая скажет "всё, комьюнити, берём лопаты и идём разгребать эту кучу г… ениальных разрозненных кусков текста."


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


Документация


  • Tutorial 1, Tutorial 2 — с них начинать
  • Nim in action — толковая книжка, которая действительно хорошо объясняет многие аспекты языка, порой намного лучше оф. документации
  • Nim manual — собственно, мануал — описано практически всё, но нет
  • Nim experimental manual — а почему бы, собственно, не продолжить документацию на отдельной страничке?
  • The Index — тут собраны ссылки на всё, то есть вообще всё что можно найти в nim'е. Не нашли нужного в туториалзах и мануале — в индексе точно найдёте.

Уроки и туториалы


  • Nim basics — самые основы для новичков, сложные темы не раскрыты
  • Nim Days — небольшие проекты (live examples)
  • Rosetta Code — очень прикольно сравнивать решение одних и тех же задач на разных ЯП, в т.ч. nim
  • Exercism.io — здесь можно пройти "путь nim", выполняя задания
  • Nim by Example

Помощь


  • IRC — основное место обитания… ниммеров?, которое транслируется в Discord и Gitter. Никогда не пользовался IRC (да и сейчас не пользуюсь). Вообще это очень странный выбор. Есть ещё голубиная почта по ниму… ладно, шучу.
  • Nim forum Возможности форума минимальны, но 1) тут можно найти ответ 2) тут можно задать вопрос, если п.1 не сработал 3) вероятность ответа больше 50% 4) на форуме сидят разработчики языка и активно отвечают. Кстати, форум написан на nim, и поэтому функциональность никакая
  • Nim telegram group — есть возможность задать вопрос и [не]получить ответ.
  • Есть ещё русская телеграм-группа, если вы устали от nim и не хотите о нём ничего слышать — вам туда :) (отчасти шутка)

Playground


  • Nim playground — тут можно запустить программу на nim прямо в браузере
  • Nim docker cross-compiling — тут можно почитать, как запустить докер-образ и скомпилировать программу для разных платформ.

Пакеты


  • nimble.directory — тут собраны все опубликованные пакеты, доступные для установки через пакетный менеджер nimble.
  • Curated list of packages — собранный энтузиастами список более-менее живых пакетов

Переход на nim с других языков



Что нравится


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


Фрактал сложности


Nim предоставляет вам "фрактал сложности". Вы можете писать высокоуровневый код. Можете бодаться с сырыми указателями и радоваться каждой attempt to read from nil. Можете вставлять C-код. Можете писать вставки на ассемблере. Можете писать процедуры (static dispatch). Не хватает — есть "методы" (dynamic dispatch). Ещё? Есть дженерики, и есть дженерики, мимикрирующие под функции. Есть шаблоны (templates) — механизм замены, но не такой блевотный, как в C++ (там макросы — это всё ещё просто текстовая замена, или уже что-то поумнее?). Есть макросы, в конце концов — это как IDDQD, они включают режим бога и позволяют работать напрямую с AST и буквально заменять куски синтаксического дерева, или самостоятельно расширять язык как хотите.
То есть на "высоком" уровне вы можете писать хелловорлды и горя не знать, но никто вам не запрещает проводить махинации любой сложности.


Скорость разработки


Кривая обучения — не кривая. Это прямая. Установив nim, вы в первую же минуту запустите ваш первый hello world, а в первый же день вы напишете простую утилиту. Но и через пару месяцев вам будет что изучать. Например, я начинал с процедур, потом мне понадобились методы, через какое-то время мне очень пригодились дженерики, недавно я открыл для себя шаблоны в полной их красе, и при этом я ещё вообще не трогал макросы. Сравнивая с тем же rust или c++, "влиться" в nim гораздо проще.


Package management


Есть package manager под названием nimble, которые умеет устанавливать, удалять, создавать пакеты и подгружать зависимости. Когда создаёте свой пакет (= проект), в nimble можно прописать разные задачи (при помощи nimscript, который подмножество nim, исполняемый на VM), например, генерацию документации, запуск тестов, копирование ассетов итд. Nimble не только поставит нужные зависимости, но и вообще позволит сконфигурировать рабочее окружение для вашего проекта. То есть nimble — это, грубо говоря, CMake, который написали не извращенцы, а нормальные люди.


Читаемость и выразительность


Внешне nim очень похож на python с type annotations, хотя nim это не python вообще ни разу. Питонистам придётся забыть динамическую типизацию, наследование, декораторы и прочие радости, и вообще перестроить мышление. Не стоит пытаться перенести свой python-опыт в nim, ибо разница слишком большая. Поначалу очень хочется гетерогенных коллекций и миксинов с декораторами. но потом как-то привыкаешь жить в лишениях :)


Вот пример программы на nim:


    type
      NumberGenerator = object of Service  # this service just generates some numbers

      NumberMessage = object of Message
        number: int

    proc run(self: NumberGenerator) =
      if not waitAvailable("calculator"):
        echo "Calculator is unavailable, shutting down"
        return

      for number in 0..<10:
        echo &"Sending number {number}"
        (ref NumberMessage)(number: number).send("calculator")

Модульность


Всё разбито на модули, которые можно как угодно импортировать — импортировать только определённые символы, или все кроме определённых, или все, или ни одного и заставить пользователя указывать полный путь а-ля module.function(), и ещё импортировать под другим именем. Разумеется, всё это многообразие очень пригодится как ещё один агрумент в споре "какой язык программирования лучше", ну а в своём проекте вы будете тихонько везде писать import mymodule и о других вариантах не вспоминать.


Method call syntax


Вызов функции может быть записан по-разному:


    double(2)
    double 2
    2.double()
    2.double

С одной стороны, теперь каждый… пишет как ему нравится (а всем нравится по-разному, разумеется, причём по-разному даже в рамках одного проекта). Но зато все функции могут быть записаны как вызов метода, что очень сильно улучшает читаемость. В питоне может быть такое:


list(set(some_list))  # араб-стайл: читаем справа налево, а ещё можно добавить map и filter и уехать в дурку

Тот же код в nim можно было бы переписать более логично:


some_list.set.list  # читаем слева направо

ООП


ООП хоть и присутствует, но отличается от оного в плюсах и питоне: объекты и методы — разные сущности, и вполне могут существовать в разных модулях. Более того, вы можете написать свои методы для базовых типов вроде int


    proc double(number: int): int =
        number * 2

    echo $2.double()  # prints "4"

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


# sharedtables.nim
type SharedTable*[A, B] = object ## generic hash SharedTable
    data: KeyValuePairSeq[A, B]
    counter, dataLen: int
    lock: Lock

Тип SharedTable* помечен звёздочкой, значит, он "виден" в других модулях и его можно импортировать. Но вот data, counter и lock — приватные члены, и "снаружи" sharedtables.nim они недоступны. Это меня очень обрадовало, когда я решил написать некоторые дополнительные функции для типа SharedTable, навроде len или hasKey, и обнаружил, что у меня нет доступа ни к counter, ни к data, и единственный способ "расширить" SharedTable — написать свой, с бл


Вообще наследование используется намного реже, чем в том же питоне (по личному опыту), потому что есть method call syntax (см. выше) и Object Variants (см ниже). Путь nim — это скорее композиция, а не наследование. Так же и с полиморфизмом: в nim'е есть методы, которые могут быть переопределены в классах-наследниках, но это нужно явно указать при компиляции, используя флаг --multimethods:on. То есть по умолчанию методы не работают, что слегка подталкивает к работе без оных.


Compile-time execution


Const — возможность вычислять что-то на этапе компиляции и "зашивать" это в результирующий бинарник. Это круто и удобно. Вообще в nim особое отношение ко "времени компиляции", даже есть ключевое слово when — это как if, но сравнение идёт на этапе компиляции. Можно написать что-то вроде


  when defined(SDL_VIDEO_DRIVER_WINDOWS):
    import windows  ## oldwinapi lib
  elif defined(SDL_VIDEO_DRIVER_X11):
    import x11/x, x11/xlib  ## x11 lib

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


Reference type


Ref type — аналог shared_ptr в C++, о котором позаботится сборщик мусора. Но можно и самому вызывать сборщик мусора в те моменты, когда это вам удобно. А можно попробовать разные варианты сборщиков мусора. А можно вообще отключить сборщик мусора и использовать обычные указатели.


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


Lambdas


Есть анонимные процедуры (aka лямбды в питоне), но в отличие от питона в анонимной процедуре можно использовать несколько statements:


someProc(callback=proc(a: int) -> int = var b = 5*a; result = a)

Exceptions


Есть исключения, их очень неудобно бросать: на python raise ValueError('bad value'), на nim raise newException(ValueError, "bad value"). Больше ничего необычного — try, except, finally, всё как у всех. Я, как сторонник исключений, а не кодов ошибок, ликую. Кстати, для функций можно указывать, какие исключения они могут бросить, и компилятор будет это проверять:


proc p(what: bool) {.raises: [IOError, OSError].} =
  if what: raise newException(IOError, "IO")
  else: raise newException(OSError, "OS")

Generics


Дженерики очень выразительные, например, можно ограничивать возможные типы


proc onlyIntOrString[T: int|string](x, y: T) = discard  # только int и string

А можно передавать тип вообще как параметр — выглядит как обычная функция, а на самом деле дженерик:


proc p(a: typedesc; b: a) = discard
# is roughly the same as:
proc p[T](a: typedesc[T]; b: T) = discard
# hence this is a valid call:
p(int, 4)
# as parameter 'a' requires a type, but 'b' requires a value.

Templates


Шаблоны (templates) — что-то вроде макросов в C++, только сделанных правильно :) — вы можете безопасно передавать в шаблоны целые блоки кода, и не думать о том, что подстановка что-то испортит в outer коде (но можно, опять же, сделать, чтобы испортила, если очень надо).


Вот пример шаблона app, который в зависимости от значения переменной вызывает один из блоков кода:


template app*(serverCode: untyped, clientCode: untyped) =
    # ...
    case mode
      of client:
        clientCode
      of server:
        serverCode
      else:
        discard

При помощи do я могу передавать целы блоки в шаблон, например:


app do:  # serverCode
  echo "I'm server"
  serverProc()
do:  # clientCode
  echo "I'm client"
  clientProc()

Interactive shell


Если нужно быстро что-то протестировать, то есть возможность вызвать "интерпретатор" или "nim shell" (как если вы запустите python без параметров). Для этого воспользуйтесь командой nim secret или скачайте пакет inim.


FFI


FFI — возможность взаимодействовать со сторонними библиотеками на C/C++. К сожалению, для использования внешней библиотеки вы должны написать враппер, объясняющий, откуда и что импортировать. Например:


{.link: "/usr/lib/libOgreMain.so".}
type ManualObjectSection* {.importcpp: "Ogre::ManualObject::ManualObjectSection", bycopy.} = object

Есть инструменты, делающие этот процесс полуавтоматическим:



Что не нравится


Сложность


Слишком много всего. Язык задумывался как минималистичный, но сейчас это очень далеко от правды. Вот например за что мы получили code reordering?!


Избыточность


Много говнища: system.addInt — "Converts integer to its string representation and appends it to result". Мне кажется, это очень удобная функция, я её использую в каждом проекте. Вот ещё интересное: fileExists and existsFile (https://forum.nim-lang.org/t/3636)


Нет унификации


"There's only one way to do smth" — вообще нет:


  • Method call syntax — пиши вызов функции как хочешь
  • fmt vs &
  • camelCase и underscore_notation
  • this и tHiS (спойлер: это одно и то же)
  • function vs procedure vs template

Баги (нет, БАГИ!)


Баги есть, примерно 1400. Или просто зайдите на форум — там постоянно какие-то баги находят.


Стабильность


В дополнение к предыдущему пункту, v1 подразумевает стабильность, да? И тут на форум залетает создатель языка Araq и говорит: "чуваки, я тут запилил ещё один (шестой) сборщик мусора, он круче, быстрее, молодёжнее, даёт вам shared memory для потоков (ха-ха, а раньше для этого вы страдали и использовали костыли), качайте develop ветку и пробуйте". И все такие "Вау, как круто! А что это значит для простых смертных? Нам теперь опять весь код менять?" Вроде как нет, поэтому я обновляю nim, запускаю новый сборщик мусора --gc:arc и моя программа падает где-то на этапе компиляции c++ кода (т.е. не в nim, а в gcc):


/usr/lib/nim/system.nim:274:77: error: ‘union pthread_cond_t’ has no member named ‘abi’
  274 |   result = x

Великолепно! Теперь вместо того, чтобы писать новый код, я должен чинить старый. Не от этого ли я бежал, когда выбирал nim?


Приятно осознавать, что я не один


Методы и многопоточность


По умолчанию флаги multimethods и threads выключены — вы ведь не собираетесь в 2019 2020 году писать многопоточное приложение с переопределением методов?! А уж как здорово, если ваша библиотека создавалась без учёта потоков, а потом пользователь их включил… Ах да, для наследования есть замечательные прагмы {.inheritable.} и {.base.}, чтобы ваш код не был слишком лаконичен.


Object variants


Вы можете избежать наследования, используя т.н. object variants:


type
  CoordinateSystem = enum
    csCar, # Cartesian
    csCyl, # Cylindrical

  Coordinates = object
    case cs: CoordinateSystem: # cs is the coordinate discriminator
      of csCar:
        x: float
        y: float
        z: float
      of csCyl:
        r: float
        phi: float
        k: float

В зависимости от значения cs, вам будут доступны либо x, y, z поля, либо r, phi и k.


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


type

  # The 3 notations refer to the same 3-D entity, and some coordinates are shared
  CoordinateSystem = enum
    csCar, # Cartesian    (x,y,z)
    csCyl, # Cylindrical  (r,?,z)

  Coordinates = object
    case cs: CoordinateSystem: # cs is the coordinate discriminator
      of csCar:
        x: float
        y: float
        z: float  # z already defined here
      of csCyl:
        r: float
        phi: float
        z: float  # fails to compile due to redefinition of z

Do notation


Просто процитирую:


  • do with parentheses is an anonymous proc
  • do without parentheses is just a block of code
    Одно выражение означает разные вещи ?_(?)_/?

Когда что использовать


Итак, у нас есть функции, процедуры, дженерики, мультиметоды, шаблоны и макросы. Когда лучше использовать шаблон, а когда процедуру? Шаблон или дженерик? Функция или процедура? Так, а макросы? Я думаю, вы поняли.


Custom pragma


В питоне есть декораторы, которые можно применять хоть к классам, хоть к функциям.
В nim для этого есть прагмы. И вот что:


  • Вы можете написать свою прагму, которая будет декорировать процедуру:
    proc fib(n : int) : int {.cached.} =
    # do smth
  • Вы не можете написать свою прагму, которая будет декорировать тип (=класс).

Nimble


Что мертво — умереть не может. В nimble куча проектов, которые уже давно не обновлялись (а в nim это смерти подобно) — и их не убирают. Никто за этим не следит. Понятно, обратная совместимость, "нельзя просто взять и удалить пакет из репы", но всё же… Ладно, спасибо, что хоть не как npm.


Дырявые абстракции


Есть такой закон дырявых абстракций — вы используете какую-то абстракцию, но рано или поздно вы обнаружете в ней "дыру", которая приведёт вас на уровень ниже. Nim — это абстракция над C и C++, и рано или поздно вы туда "провалитесь". Спорим, вам там не понравится?


Error: execution of an external compiler program 'g++ -c  -w -w -fpermissive -pthread   -I/usr/lib/nim -I/home/user/c4/systems/network -o 
/home/user/.cache/nim/enet_d/@m..@s..@s..@s..@s..@s..@s.nimble@spkgs@smsgpack4nim-0.3.0@smsgpack4nim.nim.cpp:6987:136: note:   initializing argument 2 of ‘void unpack_type__k2dhaoojunqoSwgmQ9bNNug(tyObject_MsgStreamcolonObjectType___kto5qgghQl207nm2KQZEDA*, NU&)’
 6987 | N_LIB_PRIVATE N_NIMCALL(void, unpack_type__k2dhaoojunqoSwgmQ9bNNug)(tyObject_MsgStreamcolonObjectType___kto5qgghQl207nm2KQZEDA* s, NU& val) { nimfr_("unpack_type", "/home/user/.nimble/pkgs/msgpack4nim-0.3.0/msgpack4nim.nim");
      |                                                                                                                       

/usr/bin/ld: /home/user/.cache/nim/enet_d/stdlib_dollars.nim.cpp.o: in function `dollar___uR9bMx2FZlD8AoPom9cVY9ctA(tyObject_ConnectMessage__e5GUVMJGtJeVjEZUTYbwnA*)':
stdlib_dollars.nim.cpp:(.text+0x229): undefined reference to `resizeString(NimStringDesc*, long)'
/usr/bin/ld: stdlib_dollars.nim.cpp:(.text+0x267): undefined reference to `resizeString(NimStringDesc*, long)'
/usr/bin/ld: stdlib_dollars.nim.cpp:(.text+0x2a2): undefined reference to `resizeString(NimStringDesc*, long)'

Итак


Я тупой программист. Я не хочу знать, как работает GC, что там и как линкуется, куда кэшируется и как убирается мусор. Это как с машиной — я в принципе знаю, как она устроена, немного про сход-развал, немного про коробку передач, масло там надо заливать и прочее, но вообще я просто хочу сесть и ехать (причём быстро) на вечеринку. Машина — не цель, а средство достижения цели. Если она сломается — я не хочу лезть в капот, а просто отвезу её на сервис (в смысле, открою issue на гитхабе), и было бы здорово, если бы чинили её быстро.


Nim должен был стать такой машиной. Отчасти он и стал, но в то же время, когда я мчусь на этой машине по хайвею, у меня отваливается колесо, а заднее зеркало показывает вперёд. За мной бегут инженеры и на ходу что-то приделывают ("теперь с этим новым спойлером ваша машина ещё быстрее"), но от этого у меня отваливается багажник. И знаете что? Мне всё равно чертовски нравится эта машина, ведь это лучшая из всех машин, что я видел.

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


  1. Alesh
    28.12.2019 19:12

    Когда я первый раз увидел bin, у меня первый раз возникло желание написать свой язык. Там были просто гениальные идеи, и были такие как синтаксис вызова методов. Когда метод может быть вызван четырьмя вариантами, пример есть в почте автора, у вас будет четыре не пересекающихся группы программистов на нем. Почему это плохо.
    Вспомните почему заказали Perl.


    1. kesn Автор
      28.12.2019 21:20

      Почему не пересекающихся? :) Я в проекте использую 3 из 4 перечисленных.


      Ну например для логирования я не использую скобки:


      logging.debug "Oops"  # better
      logging.debug("Oops")  # worse but not restricted
      "Oops".debug()  # wat
      "Oops".debug  # insane

      Type convertion удобно писать так:


      someProc(10.unit8)  # better
      someProc(unit8(10))  # worse but not restricted

      Для методов (т.е. процедур, которые относятся к какому-то объекту) я использую первое:


      someObject.someProc(arg1: 5, arg2: 10)  # better
      someProc(someObject, arg1: 5, arg2: 10)  # worse but not restricted

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


      1. KMiNT21
        28.12.2019 21:01

        Т.е. правило «пиши как лучше читается». Хорошо это или плохо — пока не знаю, но более склоняюсь к «строгому» языку, чем к таким вольностям.

        А для меня это киллер-фича. :) Самое главное для меня — это как логично(и быстро) читается исходник.

        И по поводу отступов — вот тут как раз «строгость» в тему. Это тоже в плюс читаемости.


    1. Siemargl
      28.12.2019 00:41

      Это называется UFSC
      Для ФП стиля очень удобно.

      Вообще, Nim стоит поздравить с релизом.


      1. kesn Автор
        28.12.2019 01:11

        Это называлось UFCS в ниме раньше. Потом они стали называть это Method Call Syntax. Потому что могут ?_(?)_/?


  1. ssurrokk
    28.12.2019 20:40

    Было бы неплохо выделить киллер-фичи Nim, для тех кто не может выделить их сам из статьи, например для меня)


    1. kesn Автор
      28.12.2019 20:57

      Сильвупле (имхо):


      1. Быстрая компиляция в быстрый нативный код
      2. Доступна вся экосистема C/C++
      3. Простой в освоении
      4. Позволяет писать код любой сложности


      1. Amomum
        28.12.2019 21:27

        Если С++ ругают за медленную компиляцию, то как компиляция Nim (которая ведь транспиляция в С++ сперва) может быть быстрой?


        1. kesn Автор
          28.12.2019 21:42

          В этом плане, увы, я могу оперировать только собственным опытом — деталей реализации я не знаю. Разумеется, компиляция не может быть быстрее копиляции C++ кода, но, во-первых, есть кэш компиляции, так что вы не будете пересобирать мир при небольших изменениях, а во-вторых, одно дело когда C++ код пишете вы для себя (вам его ещё читать потом), а другое дело когда это делает транспайлер и он может оптимизировать как хочет:


          struct tySequence__K284t8D0DApfLbw9c7cpnKw {
            NI len; tySequence__K284t8D0DApfLbw9c7cpnKw_Content* p;
          };
          struct NimStrPayload {NI cap;
          AllocatorObj* allocator;
          NIM_CHAR data[SEQ_DECL_SIZE];
          };
          struct NimStringV2 {NI len;
          NimStrPayload* p;
          };
          typedef N_NIMCALL_PTR(void, tyProc__l0xby6CKnyVDN9bJs3WwRgw) (NI16 entity);
          typedef NU8 tyEnum_Level__pW4mH4lipH6u2NKDGEWdGg;
          typedef NimStringV2 tyArray__nHXaesL0DJZHyVS07ARPRA[1];
          struct TNimType {void* destructor;
          NI size;
          NCSTRING name;
          void* traceImpl;
          void* disposeImpl;
          };

          Но это не точно.


          1. Amomum
            28.12.2019 21:45

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


        1. potan
          29.12.2019 01:07

          Медленая компиляция идет в основном от сложного использования шаблонов. Если nim генерирует простой C-подобный код с минимальным использованием фич из C++ компиляция будет вполне быстрой.


          1. Amomum
            29.12.2019 01:22

            Ну, да, я вроде уже сам додумался чуть выше, спасибо :)


      1. dimaaan
        28.12.2019 21:55

        Позволяет писать код любой сложности

        Любой тьюринг-полный язык позволяет писать код любой сложности


        1. kesn Автор
          28.12.2019 01:16
          +1

          Согласен, тут не хватает какого-то слова, но мне казалось, что статья доносит посыл.


          Позволяет эффективно писать код любой сложности.


        1. zuko3d
          28.12.2019 03:02
          +1

          Как вы на Питоне напишете драйвера для принтера?


          1. leotsarev
            28.12.2019 12:12

            А что? Не знаю, как в Linux, но в Винде драйвер принтера это user-mode program. Оберточку сделать и все, запускай Питон


            1. leotsarev
              28.12.2019 12:12

              Не то, чтобы стоит так делать, конечно


  1. xomachine
    28.12.2019 21:04

    Итак, у нас есть функции, процедуры, дженерики, мультиметоды, шаблоны и макросы. Когда лучше использовать шаблон, а когда процедуру? Шаблон или дженерик? Функция или процедура? Так, а макросы?


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


    1. kesn Автор
      28.12.2019 21:29

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


      Например, сначала у меня был бесконечный цикл (gamedev, как-никак), который был функцией, принимающей коллбек:


      proc loop(frequency: float, callback: proc(dt)) =
        # ...
        var dt = ...
        callback(dt)
      
      proc doSmth(dt: float) =
        echo &"{dt} seconds passed in this loop iteration"
      
      loop(30, doSmth)

      А потом я вдруг понял, что с шаблоном будет проще и быстрее:


      template loop(frequency: float, code: untyped) =
        # ...
        var dt {.inject.} = ...
        code
      
      loop(30) do:
        echo &"{dt} seconds passed in this loop iteration"


  1. george3
    28.12.2019 08:09
    +2

    Nim не первый и не лучший(ИМХО)кто пытается сделать ядреный сплав простоты и эффективности (Pythonic-like, macros, compiled,..). Был такой Boo (остановился в 2010 из-за перехода автора в Unity), имел на 25% короче синтаксис, макросы, работал на .Net. Все было круто до 2010, поддержка со стороны SharpDevelop лучше чем у Nim сейчас. Был Nererle c директивой pragma=indent программы были размером равны аналогу на Python, царь метапрограммирования. Убит Microsoftом ввиду опасности для С# и F# и прочих его унылых языков. Авторы взяты на работу в MS на левые проекты за мешок денег. Cobra,… все ушли. Слишком инертный мир в программинге. Слишком.


    1. chapuza
      28.12.2019 20:00
      +1

      C#, F#

      Вот так, через запятую? Вы F# хотя бы издалека видели?


    1. IL_Agent
      29.12.2019 14:38

      Тоже вспоминаю Nemerle. Очень жаль, что закопали. По сей день смотрится очень круто.


  1. JekaMas
    28.12.2019 12:49

    К сожалению, поддержка от Status проекта nim не вечна. Работал там, в течение последнего года компания оптимизируется и оптимизируется, судя по финансам, в новом году оптимизируется окончательно.
    И это определённая проблема nim. Интересный язык, но нужна поддержка компаний.


    1. kesn Автор
      28.12.2019 13:13

      В точку. Сравнивая с rust, я прям вижу, как проекту не хватает внимания, как со стороны разработчиков, так и со стороны компаний.


      1. Siemargl
        28.12.2019 23:58
        +2

        >внимание со стороны разработчиков

        я сколько ни смотрел на ним, я не понял, зачем он. точка.

        нужен понятный таргетинг и обоснование


        1. kesn Автор
          29.12.2019 00:02
          -1

          Ну, знаете… Зачем плюсы?


          1. Siemargl
            29.12.2019 09:53

            Десктоп, веб, эмбеддед, энтерпрайз, геймдев, расчеты — а что может Ним?

            Для каждой области нужен свой тулинг, библиотеки


            1. kesn Автор
              29.12.2019 14:36

              Скажу так: ним может всё, что могут C и C++. По собственному опыту не могу сказать, что есть прям какая-то ниша — nim это general purpuse.
              Desktop — да, есть несколько библиотек для гуя, но можно написать свой враппер для любой либы из c/c++.
              Web — есть jester и karax (фреймворки на ниме), есть всякие сокеты и вебсерверы в стандартной библиотеке. Но можно написать свой враппер для любой либы из c/c++.
              Эмбеддед — можно, там надо спец флаги указать.
              Геймдев — можно, пишу сейчас сам. Ядро на ниме, графика — на ogre3d или sdl, ввод и окна — на sdl, сеть — на enet (но есть и сетевые библиотеки на pure nim).
              Расчёты — есть куча всего, типа arraymancer, neo и что-то ещё. Но можно написать свой враппер для… Вы поняли :)


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


  1. Co0l3r
    28.12.2019 14:12

    Что меня больше всего поразило в Nim, когда я некоторое время назад на него смотрел — то что язык позиционируется как замена C/C++ и при этом там есть сборка мусора (но уже какбы нет), причем там целая куча разных сборщиков на выбор, но ни по одному не найти нормальной документации, того какие гарантии риалтайма даются, и как они обеспечивается.

    image

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

    Сейчас автор языка решил всё таки сделать reference counting основным режимом, но из-за этого почему-то отвалилось куча всего и вылезло дикое количество багов. Очень странно называть язык в таком состоянии 1.0.


    1. kesn Автор
      28.12.2019 14:42

      Стоит отметить, что в версии 1.0 по умолчанию старый сборщик мусора, с которым всё норм. Новый сборщик ещё не пришёл на замену, он включается опционально. Проблема именно в количестве сборщиков мусора (при том они разные: shared heap vs per-thread heap), и в том, что сейчас появился ещё один, вместо того чтобы делать реально полезные вещи.


      1. Co0l3r
        28.12.2019 19:17

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


    1. 0xd34df00d
      31.12.2019 08:12

      Заинтересовал источник данных для последней строки, а на сайте я эту картинку сходу не нашел. Не поделитесь ссылкой?



  1. tyderh
    28.12.2019 18:15

    Самое яркое впечатление от моего опыта с nim: ты пытаешья сделать что-то незадокументированное (или просто совершаешь ошибку) и в ответ получаешь километровый стектрейс… который абсолютно нечитаем и абсолютно не помогает в решении проблемы. И приходится разбираться в этом самом "фрактале сложности", не всегда с успехом.


    Следствие от того же самого: если какая-то фича в библиотеке не задокументирована, то понять, как её использовать можно только с очень большим трудом (исходники не особо-то и помогают).


    1. kesn Автор
      29.12.2019 14:43

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


  1. KMiNT21
    28.12.2019 20:54
    +1

    но вообще я просто хочу сесть и ехать (причём быстро) на вечеринку. Машина — не цель, а средство достижения цели.

    но от этого у меня отваливается багажник. И знаете что? Мне всё равно чертовски нравится эта машина, ведь это лучшая из всех машин, что я видел.

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


    1. kesn Автор
      29.12.2019 14:54

      Нет, на другой (c++) я бы так далеко никогда не доехал :)
      Жаль, что у вас был негативный опыт. Попробуйте v1 — всё-таки разрабы проделали много работы над стабильностью. Я тут конечно угораю над ними в статье, но вообще стало лучше.


  1. Centimo
    29.12.2019 02:26

    Есть шаблоны (templates) — механизм замены, но не такой блевотный, как в C++ (там это всё ещё просто текстовая замена, или уже что-то поумнее?).

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


    1. xomachine
      29.12.2019 11:14

      По моему мнению шаблоны в плюсах плохи:
      1. Временем компиляции
      2. Негибкостью (хороши только если одну функцию надо написать для нескольких типов, но как только начинают решаться проблемы из реального мира, приходится изобретать монструозную конструкцию из шаблонов, а то и вообще откатываться к макросам из C)
      3. Сообщениями об ошибках (без парсера в них можно утонуть)

      В Nim метапрограммирование на порядок лучше плюсового реализовано.


    1. kesn Автор
      29.12.2019 13:36

      Я извиняюсь, кажется, я налажал в терминологии. Я имел ввиду механизм подстановки, когда пишешь #define one two, и препроцессор не глядя заменяет одно на другое. В Ниме templates — шаблоны — как раз делают постановку кода, вот меня и переклинило.
      Если я всё правильно понял, то шаблоны из c++ — это generic в ниме.


      1. Aldrog
        31.12.2019 14:11

        В C++ шаблоны позволяют подставлять в код типы или значения времени компиляции (constexpr).


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


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