Язык программирования 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(c)]
result = not result
# String conversion proc $, automatically called by echo
proc `$`(c: CRC32): string = int64(c).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 .. <len:
result[i] = init
result
# 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: "<stdio.h>", 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 0x06_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, ваш код будет работать практически везде.
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий