[Перевод] Что такого особенного в Nim?

61655b6ee8f6472d96d54e2184a0917d.png Язык программирования Nim (ранее именовался Nimrod) — захватывающий! В то время как официальная документация с примерами плавно знакомит с языком, я хочу быстро показать вам что можно сделать с Nim, что было бы труднее или невозможно сделать на других языках.

Я открыл для себя Nim, когда искал правильный инструмент для написания игры, HoorRace, преемник моей текущей DDNet игры/мода Teeworlds.

(прим. пер. На синтаксис Nim имели влияние Modula 3, Delphi, Ada, C++, Python, Lisp, Oberon.)

Да, эта часть всё ещё не захватывает, но просто следите за продолжением поста: for i in 0…10: echo «Hello World»[0…i] Для запуска, естественно, потребуется компилятор Nim (прим. пер. в ArchLinux, например, пакет есть community/nim). Сохраните этот код в файл hello.nim, скомпилируйте его при помощи nim c hello.nim, и, наконец, запустите исполняемый файл ./hello. Или воспользуйтесь командой nim -r c hello.nim, которая скомпилирует и запустит полученный файл. Для сборки оптимизированной версии воспользуйтесь командой nim -d: release c hello.nim. После запуска вы увидите вот такой вывод в консоль: H He Hel Hell Hello Hello Hello W Hello Wo Hello Wor Hello Worl Hello World

Для реализации эффективной процедуры CRC32 вам понадобится предвычисленная таблица. Вы можете её вычислить во время выполнения программы или сохранить её в коде в виде магического массива. Конечно мы не хотим магических цифр в нашем коде, так что мы будем вычислять таблицу на запуске программы (по крайней мере сейчас): import unsigned, strutils

type CRC32* = uint32 const initCRC32* = CRC32(-1)

proc createCRCTable (): array[256, CRC32] = for i in 0…255: var rem = CRC32(i) for j in 0…7: if (rem and 1) > 0: rem = (rem shr 1) xor CRC32(0xedb88320) else: rem = rem shr 1 result[i] = rem

# Table created at runtime var crc32table = createCRCTable ()

proc crc32(s): CRC32 = result = initCRC32 for c in s: result = (result shr 8) xor crc32table[(result and 0xff) xor ord©] result = not result

# String conversion proc $, automatically called by echo proc `$`(c: CRC32): string = int64©.toHex (8)

echo crc32(«The quick brown fox jumps over the lazy dog») Отлично! Это работает и мы получили 414FA339. Однако, было бы гораздо лучше, если бы мы могли вычислить CRC таблицу во время компиляции. И в Nim это можно сделать очено просто, заменяем нашу строку с присвоением crc32table на следующий код:

# Table created at compile time const crc32table = createCRCTable () Да, верно, всё что нам нужно сделать, так это заменить var на const. Прекрасно, не правда ли? Мы можем писать один и тот же код, который можно исполнять как в работе программы, так и на этапе компиляции. Никакого шаблонного метапрограммирования. Шаблоны и макросы могут быть использованы для избегания копирования и лапши в коде, при этом они будут обработаны на этапе компиляции.Темплейты просто заменяются на вызовы соответствующих функций во время компиляции. Мы можем определить наши собственные циклы вот так:

template times (x: expr, y: stmt): stmt = for i in 1…x: y

10.times: echo «Hello World» Компилятор преобразует times в обычный цикл:

for i in 1…10: echo «Hello World» Если вас заинтересовал синтаксис 10.times, то знайте, что это просто обычный вызов times с первым аргументом 10 и блоком кода в качестве второго аргумента. Вы могли просто написать: times (10):, подробнее смотрите о Unified Call Syntax ниже.

Или инициализируйте последовательности (массивы произвольной длинны) удобнее:

template newSeqWith (len: int, init: expr): expr = var result = newSeq[type (init)](len) for i in 0 …

# Create a 2-dimensional sequence of size 20,10 var seq2D = newSeqWith (20, newSeq[bool](10))

import math randomize () # Create a sequence of 20 random integers smaller than 10 var seqRand = newSeqWith (20, random (10)) echo seqRand Макрос заходит на шаг дальше и позволяет вам анализировать и манипулировать AST. Например, в Nim нет списковых включений (прим. пер. list comprehensions), но мы можем добавить их в язык при помощи макроса. Теперь вместо:

var res: seq[int] = @[] for x in 1…10: if x mod 2 == 0: res.add (x) echo res

const n = 20 var result: seq[tuple[a, b, c: int]] = @[] for x in 1…n: for y in x…n: for z in y…n: if x*x + y*y == z*z: result.add ((x, y, z)) echo result Вы можете использовать модуль future и писать:

import future echo lc[x | (x <- 1..10, x mod 2 == 0), int] const n = 20 echo lc[(x,y,z) | (x <- 1..n, y <- x..n, z <- y..n, x*x + y*y == z*z), tuple[a,b,c: int]]

Вместо оптимизации своего кода, не предпочли бы вы сделать компилятор умнее? В Nim это возможно! var x: int for i in 1…1_000_000_000: x += 2 * i echo x Этот (достаточно бесполезный) код может быть ускорен при помощи обучения компилятора двум оптимизациям: template optMul{`*`(a,2)}(a: int): int = let x = a x + x

template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b * a В первом шаблоне мы указываем, что a * 2 может быть заменено на a + a. Во втором шаблоне мы указываем что int-переменные могут быть поменяны местами, если первый агрумент — число-константа, это нужно чтобы мы могли применить первый шаблон.Более сложные шаблоны также могут быть реализованы, например, для оптимизации булевой логики:

template optLog1{a and a}(a): auto = a template optLog2{a and (b or (not b))}(a, b): auto = a template optLog3{a and not a}(a: int): auto = 0

var x = 12 s = x and x # Hint: optLog1(x) --> «x» [Pattern]

r = (x and x) and ((s or s) or (not (s or s))) # Hint: optLog2(x and x, s or s) --> «x and x» [Pattern] # Hint: optLog1(x) --> «x» [Pattern]

q = (s and not x) and not (s and not x) # Hint: optLog3(s and not x) --> »0» [Pattern] Здесь s оптимизируется до x, r тоже оптимизируется до x, и q сразу инициализируется нулём. Если вы хотите увидеть как применяются шаблоны для избегания выделения bigint, посмотрите на шаблоны, начинающиеся с opt в библиотеке biginsts.nim:

import bigints

var i = 0.initBigInt while true: i += 1 echo i

Так как Nim транслируется в C (C++/Obj-C), использование сторонних функций не составляет никакой проблемы.Вы можете легко использовать ваши любимые функции из стандартной библиотеки:

proc printf (formatstr: cstring) {.header:»», varargs.} printf (»%s %d\n», «foo», 5) Или использовать свой собственный код, написанный на C:

void hi (char* name) { printf («awesome %s\n», name); } {.compile: «hi.c».} proc hi*(name: cstring) {.importc.} hi «from Nim» Или любой библиотеки, какой пожелаете, при помощи c2nim:

proc set_default_dpi*(dpi: cdouble) {.cdecl, importc: «rsvg_set_default_dpi», dynlib: «librsvg-2.so».}

Для достижения «soft realtime», вы можете сказать сборщику мусора когда и сколько он может работать. Основная логика игры с предотвращением вмешательства сборщика мусора может быть реализована на Nim примерно вот так: gcDisable () while true: gameLogic () renderFrame () gcStep (us = leftTime) sleep (restTime)

Часто вам может быть нужно математическое множество со значениями, которые вы определили самостоятельно. Вот так можно это реализовать с уверенностью, что типы будут проверены компилятором: type FakeTune = enum freeze, solo, noJump, noColl, noHook, jetpack

var x: set[FakeTune]

x.incl freeze x.incl solo x.excl solo

echo x + {noColl, noHook}

if freeze in x: echo «Here be freeze»

var y = {solo, noHook} y.incl 0 # Error: type mismatch Вы не можете случайно добавить значение другого типа. Внутренне это работает как эффективный битовый вектор.

То же самое возможно и с массивами, индексируйте их с помощью enum.

var a: array[FakeTune, int] a[freeze] = 100 echo a[freeze]

Это просто синтаксический сахар, но это определённо очень удобно (прим. пер. я считаю это ужасным!). В Python я всегда забываю является len и append функциями или методами. В Nim вам не нужно это помнить, потому что можно писать как угодно. Nim использует Unified Call Syntax (синтаксис унифицированного вызова), который также сейчас предложен в C++ товарищами Herb Sutter и Bjarne Stroustrup. var xs = @[1,2,3]

# Procedure call syntax add (xs, 4_000_000) echo len (xs)

# Method call syntax xs.add (0b0101_0000_0000) echo xs.len ()

# Command invocation syntax xs.add 0×06_FF_FF_FF echo xs.len

(прим. пер. этот раздел в оригинальной статье «устарел», поэтому предлагаю ссылки на оригинальный обновлённый benchmark и benchmark, приведённый в оригинале статьи)От переводчика:

Если кратко, то Nim генерирует код, который так же быстр, как и написанный человеком C/C++. Nim может транслировать код в C/C++/Obj-C (а ниже будет показано, что может и в JS) и компилировать его gcc/clang/llvm_gcc/MS-vcc/Intel-icc. Таким образом, он быстрее Go, Rust, Crystal, Java и многих других.

Nim может транслировать Nim код в JavaScript. Это позволяет писать и клиентский, и серверный код на Nim. Давайте сделаем маленький сайт, который будет считать посетителей. Это будет наш client.nim: import htmlgen, dom

type Data = object visitors {.importc.}: int uniques {.importc.}: int ip {.importc.}: cstring

proc printInfo (data: Data) {.exportc.} = var infoDiv = document.getElementById («info») infoDiv.innerHTML = p («You’re visitor number », $data.visitors, », unique visitor number », $data.uniques, » today. Your IP is », $data.ip,».») Мы определяем тип Data, который будем передавать от сервера клиенту. Процедура printInfo будет вызвана с этими данными для отображения. Для сборки нашего клиентского кода выполним команду nim js client. Результат будет сохранён в nimcache/client.js.

Для сервера нам понадобится пакетный менеджер Nimble, так как нам нужно будет установить Jester (sinatra-подобный web framework для Nim). Устанавливаем Jester: nimble install jester. Теперь напишем наш server.nim:

import jester, asyncdispatch, json, strutils, times, sets, htmlgen, strtabs

var visitors = 0 uniques = initSet[string]() time: TimeInfo

routes: get »/»: resp body ( `div`(id=«info»), script (src=»/client.js», `type`=«text/javascript»), script (src=»/visitors», `type`=«text/javascript»))

get »/client.js»: const result = staticExec «nim -d: release js client» const clientJS = staticRead «nimcache/client.js» resp clientJS

get »/visitors»: let newTime = getTime ().getLocalTime if newTime.monthDay!= time.monthDay: visitors = 0 init uniques time = newTime

inc visitors let ip = if request.headers.hasKey «X-Forwarded-For»: request.headers[«X-Forwarded-For»] else: request.ip uniques.incl ip

let json = %{«visitors»: %visitors, «uniques»: %uniques.len, «ip»: %ip} resp «printInfo ($#)».format (json)

runForever () При открытии http://localhost:5000/ сервер будет возвращать «пустую» страницу с подключёнными /client.js и /visitors. /client.js будет возвращать файл, полученный через nim js client, а /visitors будет генерировать JS код с вызовом printInfo (JSON).

Вы можете увидеть полученный Jester сайт онлайн, он будет показывать вот такую строку:

You’re visitor number 11, unique visitor number 11 today. Your IP is 134.90.126.175.

Я надеюсь, я смог заинтересовать языком программирования Nim.Обратите внимание, что язык ещё не полностью стабилен. Однако, Nim 1.0 уже не за горами. Так что это отличное время для знакомства с Nim! Бонус: так как Nim транслируется в C и зависит только от стандартной библиотеки C, ваш код будет работать практически везде.

© Habrahabr.ru