Привет, Хабр!
Уже довольно давно я пишу свой игровой фреймворк — такой 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 (по функционалу) — имхо
Документация
Вообще-то с этим плохо. Проблемы:
- Документация раскидана по разным источникам
- Документация
гавноне в полной мере описывает все возможности языка - Документация порой слишком лаконична
Пример: хотите вы написать многопоточное приложения, ядер-то много, а девать некуда.
Вот раздел официальной документации про потоки. Нет, понимаете, потоки — это отдельная большая часть языка, его фича, которую даже нужно включать флагом --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 с других языков
- Imports in Nim — статья рассказывает, как лучше всего импортировать модули и почему, позволяет на время изгнать дух питониста из себя
- Nim for python programmers
- Nim for C programmers
Что нравится
Нет смысла перечислять все возможности языка, но вот некоторые особенности:
Фрактал сложности
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)
ssurrokk
28.12.2019 20:40Было бы неплохо выделить киллер-фичи Nim, для тех кто не может выделить их сам из статьи, например для меня)
kesn Автор
28.12.2019 20:57Сильвупле (имхо):
- Быстрая компиляция в быстрый нативный код
- Доступна вся экосистема C/C++
- Простой в освоении
- Позволяет писать код любой сложности
Amomum
28.12.2019 21:27Если С++ ругают за медленную компиляцию, то как компиляция Nim (которая ведь транспиляция в С++ сперва) может быть быстрой?
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; };
Но это не точно.
Amomum
28.12.2019 21:45В целом логично, спасибо. Вероятно, генерированный код просто не будет особо напирать на "тяжелые" фишки из С++.
xomachine
28.12.2019 21:04Итак, у нас есть функции, процедуры, дженерики, мультиметоды, шаблоны и макросы. Когда лучше использовать шаблон, а когда процедуру? Шаблон или дженерик? Функция или процедура? Так, а макросы?
Функции, процедуры, дженерики, мультиметоды, шаблоны, макросы.
Именно в таком порядке по приоритету. Если первого пункта недостаточно для реализации необходимого функционала, переходим к следующему и так далее. К сожалению, не могу вспомнить где в документации видел приоритетность, но она была очень похожа на эту. Разве что мультиметодов и функций там не было. Мультиметоды я бы вообще исключил, поскольку они почти всё время были сломаны.
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"
george3
28.12.2019 08:09+2Nim не первый и не лучший(ИМХО)кто пытается сделать ядреный сплав простоты и эффективности (Pythonic-like, macros, compiled,..). Был такой Boo (остановился в 2010 из-за перехода автора в Unity), имел на 25% короче синтаксис, макросы, работал на .Net. Все было круто до 2010, поддержка со стороны SharpDevelop лучше чем у Nim сейчас. Был Nererle c директивой pragma=indent программы были размером равны аналогу на Python, царь метапрограммирования. Убит Microsoftом ввиду опасности для С# и F# и прочих его унылых языков. Авторы взяты на работу в MS на левые проекты за мешок денег. Cobra,… все ушли. Слишком инертный мир в программинге. Слишком.
IL_Agent
29.12.2019 14:38Тоже вспоминаю Nemerle. Очень жаль, что закопали. По сей день смотрится очень круто.
JekaMas
28.12.2019 12:49К сожалению, поддержка от Status проекта nim не вечна. Работал там, в течение последнего года компания оптимизируется и оптимизируется, судя по финансам, в новом году оптимизируется окончательно.
И это определённая проблема nim. Интересный язык, но нужна поддержка компаний.kesn Автор
28.12.2019 13:13В точку. Сравнивая с rust, я прям вижу, как проекту не хватает внимания, как со стороны разработчиков, так и со стороны компаний.
Siemargl
28.12.2019 23:58+2>внимание со стороны разработчиков
я сколько ни смотрел на ним, я не понял, зачем он. точка.
нужен понятный таргетинг и обоснованиеkesn Автор
29.12.2019 00:02-1Ну, знаете… Зачем плюсы?
Siemargl
29.12.2019 09:53Десктоп, веб, эмбеддед, энтерпрайз, геймдев, расчеты — а что может Ним?
Для каждой области нужен свой тулинг, библиотеки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 и что-то ещё. Но можно написать свой враппер для… Вы поняли :)
Конечно, хотелось бы видеть всё на чистом ниме, но это утопия, для этого нужно большое комьюнити, которое это всё напишет, а чтобы было большое комьюнити, нужно чтобы много всего было написано.
Co0l3r
28.12.2019 14:12Что меня больше всего поразило в Nim, когда я некоторое время назад на него смотрел — то что язык позиционируется как замена C/C++ и при этом там есть сборка мусора (но уже какбы нет), причем там целая куча разных сборщиков на выбор, но ни по одному не найти нормальной документации, того какие гарантии риалтайма даются, и как они обеспечивается.
Вот эта картинка с их сайта на самом деле сделана с отключенным сборщиком циклов, по сути это просто reference counting. Я понимаю что технически это тоже «сборка мусора», но нельзя просто одно заменять на другое, нужно чтобы программы изначально писалась с учетом этого.
Сейчас автор языка решил всё таки сделать reference counting основным режимом, но из-за этого почему-то отвалилось куча всего и вылезло дикое количество багов. Очень странно называть язык в таком состоянии 1.0.kesn Автор
28.12.2019 14:42Стоит отметить, что в версии 1.0 по умолчанию старый сборщик мусора, с которым всё норм. Новый сборщик ещё не пришёл на замену, он включается опционально. Проблема именно в количестве сборщиков мусора (при том они разные: shared heap vs per-thread heap), и в том, что сейчас появился ещё один, вместо того чтобы делать реально полезные вещи.
Co0l3r
28.12.2019 19:17Со старым сборщиком не норм то, что когда новый доделают, старый скорее всего выбросят (задепрекейтят). А с новым не то, то что его непонятно когда вообще доделают. Поэтому реально полезно было бы доделать это до конца.
0xd34df00d
31.12.2019 08:12Заинтересовал источник данных для последней строки, а на сайте я эту картинку сходу не нашел. Не поделитесь ссылкой?
tyderh
28.12.2019 18:15Самое яркое впечатление от моего опыта с nim: ты пытаешья сделать что-то незадокументированное (или просто совершаешь ошибку) и в ответ получаешь километровый стектрейс… который абсолютно нечитаем и абсолютно не помогает в решении проблемы. И приходится разбираться в этом самом "фрактале сложности", не всегда с успехом.
Следствие от того же самого: если какая-то фича в библиотеке не задокументирована, то понять, как её использовать можно только с очень большим трудом (исходники не особо-то и помогают).
kesn Автор
29.12.2019 14:43Возможно, сейчас с этим лучше. Километровые стектрейсы появляются, если всё падает в gcc, и да, это больно отлаживать. В самом же ниме ошибки вполне понятные и лично я с ними не так часто встречаюсь.
KMiNT21
28.12.2019 20:54+1но вообще я просто хочу сесть и ехать (причём быстро) на вечеринку. Машина — не цель, а средство достижения цели.
…
но от этого у меня отваливается багажник. И знаете что? Мне всё равно чертовски нравится эта машина, ведь это лучшая из всех машин, что я видел.
Но приходится бросать ее на обочине и ехать на вечеринку на другой. :)
Ох и намучился я тоже с глюками Nim. Даже при том, что очень хочется этот язык использовать, вышеупомянутые минусы заставили его отложить в сторону.kesn Автор
29.12.2019 14:54Нет, на другой (c++) я бы так далеко никогда не доехал :)
Жаль, что у вас был негативный опыт. Попробуйте v1 — всё-таки разрабы проделали много работы над стабильностью. Я тут конечно угораю над ними в статье, но вообще стало лучше.
Centimo
29.12.2019 02:26Есть шаблоны (templates) — механизм замены, но не такой блевотный, как в C++ (там это всё ещё просто текстовая замена, или уже что-то поумнее?).
Так, а чем плохи шаблоны в плюсах? Имхо явно лучше, чем в этом вашем nim, где можно пихать что угодно куда угодно.
Вообще от nim осталось впечатление какого-то франкенштейна, который сам не знает чем является.xomachine
29.12.2019 11:14По моему мнению шаблоны в плюсах плохи:
1. Временем компиляции
2. Негибкостью (хороши только если одну функцию надо написать для нескольких типов, но как только начинают решаться проблемы из реального мира, приходится изобретать монструозную конструкцию из шаблонов, а то и вообще откатываться к макросам из C)
3. Сообщениями об ошибках (без парсера в них можно утонуть)
В Nim метапрограммирование на порядок лучше плюсового реализовано.
kesn Автор
29.12.2019 13:36Я извиняюсь, кажется, я налажал в терминологии. Я имел ввиду механизм подстановки, когда пишешь #define one two, и препроцессор не глядя заменяет одно на другое. В Ниме templates — шаблоны — как раз делают постановку кода, вот меня и переклинило.
Если я всё правильно понял, то шаблоны из c++ — это generic в ниме.Aldrog
31.12.2019 14:11В C++ шаблоны позволяют подставлять в код типы или значения времени компиляции (constexpr).
Для подстановки кода в функцию, как в нимовских шаблонах, в плюсах передают аргументом лямбду, компиляторы довольно неплохо умеют такой код оптимизировать.
Макросы в плюсах приходится использовать чаще всего для генерации определений функций или типов. В Nim, если не ошибаюсь, макросы для того же используют.
Alesh
Когда я первый раз увидел bin, у меня первый раз возникло желание написать свой язык. Там были просто гениальные идеи, и были такие как синтаксис вызова методов. Когда метод может быть вызван четырьмя вариантами, пример есть в почте автора, у вас будет четыре не пересекающихся группы программистов на нем. Почему это плохо.
Вспомните почему заказали Perl.
kesn Автор
Почему не пересекающихся? :) Я в проекте использую 3 из 4 перечисленных.
Ну например для логирования я не использую скобки:
Type convertion удобно писать так:
Для методов (т.е. процедур, которые относятся к какому-то объекту) я использую первое:
Т.е. правило "пиши как лучше читается". Хорошо это или плохо — пока не знаю, но более склоняюсь к "строгому" языку, чем к таким вольностям.
KMiNT21
А для меня это киллер-фича. :) Самое главное для меня — это как логично(и быстро) читается исходник.
И по поводу отступов — вот тут как раз «строгость» в тему. Это тоже в плюс читаемости.
Siemargl
Это называется UFSC
Для ФП стиля очень удобно.
Вообще, Nim стоит поздравить с релизом.
kesn Автор
Это называлось UFCS в ниме раньше. Потом они стали называть это Method Call Syntax. Потому что могут ?_(?)_/?