Язык программирования 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


Подключайте свои C-функции и библиотеки


Так как 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)


Типобезопасные множества и enum


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

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]


Unified Call Syntax


Это просто синтаксический сахар, но это определённо очень удобно (прим. пер. я считаю это ужасным!). В 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. Как показывают искуственные тесты, Nim сравним по скорости с C/C++/D/Rust и быстрее Go, Crystal, Java и многих других.

Транслируем в JavaScript


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, ваш код будет работать практически везде.

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


  1. frol Автор
    17.05.2015 12:46
    +3

    Я познакомился с Nim вчера утром после прочтения статьи о Rust 1.0 (спасибо kstep), после прочтения которой я заинтересовался как о нём отзываются в сравнении с Go, D и прочими, вот так я наткнулся среди прочих на ЯП Nim после чего провёл практически целый день с ним :)


    1. david_mz
      17.05.2015 13:33

      Выглядит вкусно. А что на нём уже написано?


      1. frol Автор
        17.05.2015 13:36
        +1

        Самое главное, что уже написан пакетный менеджер Nimble)))

        Вот список пакетов — github.com/nim-lang/packages/blob/master/packages.json


        1. david_mz
          17.05.2015 13:39

          Нет, я имею в виду — какой существующий и работающий софт уже написан на Nim?


          1. frol Автор
            17.05.2015 13:41

            Кроме Aporia IDE я больше ничего не знаю.


          1. frol Автор
            17.05.2015 14:44

            Автор оригинальной статьи отметил, что так же был портирован NES эмулятор с Go: hookrace.net/blog/porting-nes-go-nim (демо: hookrace.net/nimes)


        1. Mingun
          18.05.2015 15:20

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


          1. kstep
            18.05.2015 15:38
            +4

            image


          1. ilammy
            18.05.2015 22:49

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


  1. valplo
    17.05.2015 13:15

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


    1. frol Автор
      17.05.2015 13:27

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

      Вот приведу цитату с сайта Nim:

      Beneath a nice infix/indentation based syntax with a powerful (AST based, hygienic) macro system lies a semantic model that supports a soft realtime GC on thread local heaps. Asynchronous message passing is used between threads, so no «stop the world» mechanism is necessary. An unsafe shared memory heap is also provided for the increased efficiency that results from that model.

      Мне кажется, что это значит, что если выключить GC, то всё равно будет использоваться общая куча. Однако, может и её можно выключить.


      1. valplo
        17.05.2015 14:13
        +1

        Если я, например, хочу написать ядро ОС, куча мне будет совсем не в тему. Можно, конечно, наваять простенький аллокатор на ассемблере…


        1. frol Автор
          17.05.2015 14:29
          +1

          Я задал вопрос про отключение кучи на github: github.com/Araq/Nim/issues/2746


          1. frol Автор
            17.05.2015 14:42
            +2

            Dennis Felsing (автор статьи оригинальной статьи) ответил (перевод):

            Да, Nim позволяет программировать низкоуровневые вещи:

            github.com/dom96/nimkernel
            github.com/ckkashyap/rustix/issues/8 (отказ от Rust в пользу Nim в портировании Unix ядра)
            hookrace.net/blog/nim-binary-size
            github.com/def-/nim-small-coreutils

            Ещё был вопрос какие проекты уже были написаны на Nim:

            hookrace.net/blog/porting-nes-go-nim


      1. kstep
        17.05.2015 14:16
        +3

        Использование кучи требует аллокатора. Если использование кучи не отключается, то некоторые системные вещи написать не получиться, например ядро ОС, низкоуровневые драйвера/фёрмварь, прошивки (embedded software).


        1. frol Автор
          17.05.2015 14:29
          +1

          Я задал вопрос про отключение кучи на github: github.com/Araq/Nim/issues/2746


          1. imwode
            17.05.2015 16:52
            +9

            Ха, намек хороший в ответе, не знаю — заметил ты или нет. Ты задаешь вопрос, типа читатели _моей_статьи интересуются. А он отвечает, ой, как здорово, спасибо за первод _моей_ статьи, рад, что у нее так много читателей :-р


            1. ForNeVeR
              17.05.2015 20:14
              +1

              Так статья-то, вроде как, перевод? Вон, и ссылка стоит на оригинал; я вам больше скажу, из оригинала сюда теперь тоже стоит ссылка.


              1. imwode
                17.05.2015 20:30
                +2

                спасибо, Кэп


    1. dbelka
      17.05.2015 18:51

      Может. Вот тут можно прочитать про это nim-lang.org/docs/manual.html#types-reference-and-pointer-types


      1. dbelka
        17.05.2015 19:14

        Ой, извиняюсь, я думал вы про сборщик спрашивали.


    1. Antti
      20.05.2015 13:52

      Rust может.


      1. valplo
        20.05.2015 19:15
        -1

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


        1. Antti
          20.05.2015 19:21
          +1

          Я бы сказал что он строгий, но не переусложнённый, в нем все довольно таки четко и понятно.
          В результате расту не нужен GC.


  1. uvelichitel
    17.05.2015 17:09
    +1

    Может быть участники Nim сообщества здесь подскажут относительно concurrency model, async IO. Текущее положение и тренд разработки? Я задавал вопросы на IRC канале с полгода назад и получил ответ — «Мы работаем над этим». На ту пору разработчики склонялись в пользу native OS threads против user-space threads и библиотечных вызовов против специального синтаксиса. Как обстоят дела сейчас, где почитать?


    1. ingrysty
      17.05.2015 17:19
      -2

      Код на картинке в начале статьи посмотреть.


  1. Deepwalker
    17.05.2015 20:03
    +5

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

    + я не очень понял пассаж про трансляцию в C и почему при этом rust вдруг должен быть тормозом — rust тоже транслируется не в jvm так-то + никакого gc. Не люблю когда передергивают в свою сторону слегка лукавя или просто не разобравшись.

    Не критикую, но обратить внимание стоит на эти моменты.


    1. frol Автор
      17.05.2015 20:14

      Перефразировал предложение про призводительность. Действительно, дал маху. Спасибо, что обратили внимание.

      На счёт синтаксиса — да, мне тоже кажется, что слишком много фич, но я не берусь судить пока. Unified Call Syntax — это кошмар, я очень надеюсь на здравость рассудка людей чтобы это не попало в C++. При первом знакомстве мне показалось, что всё хорошо, но когда я начал копать примеры и проекты на Nim, я понял, что чтение у меня вызывает трудности сравнимые с Ruby. Python для меня является образцом, но и там впиливают всё больше магии с asyncio прямо в синтаксис и меня это расстраивает.


      1. Stan_1
        17.05.2015 20:24

        Опа. А я смотрю приложенный код, и у меня стойкая аналогия с Ruby. Смотрел бы «по диагонали», вообще решил бы, что что-то рубиновое описывают в статье. :) Похоже не только у меня такое ощущение. :)


        1. frol Автор
          17.05.2015 20:26

          Я даже в этой публикации использовал подсветку синтаксиса от ruby :)


  1. xiWera
    17.05.2015 20:17
    +3

    Заголовок статьи не соответсвует содержанию. По содержанию получается ответ «ничего такого особенного».


    1. frol Автор
      17.05.2015 20:22
      +3

      Во-первых, заголовок я сохранил авторский.
      Во-вторых, я бы назвал ключевые особенности такого рода: производительность программ на уровне С/С++ при «интересном» синтаксисе и возможностях языка, возможность транслирования кода в C/C++/JS.


    1. khim
      17.05.2015 21:26
      +2

      Да ладно вам. Первый приличный язык «поверх C» с нормальным метапрограммированием (как в LISP'е, когда метапрограммирование и программирование не являются двумя языками, любой модуль можно спокойно использовать во время компиляции).

      Хотя вопросов много. Как там с поддержкой больших проектов? Кросс-компиляцией? Как это вообще реализовано «внутри»? Кто-нибудь смотрел?


      1. bormotov
        17.05.2015 22:44

        мало кто помнит, что Страуструп тоже начал с трансляции в C, называлось это cfront


  1. NeoCode
    17.05.2015 21:13
    +5

    Язык интересный, как раз читаю неспеша документацию, отмечаю интересные моменты (как положительные так и отрицательные). Фич никогда много не бывает, но вот синтаксис на отступах мне категорически не нравится — слишком это неочевидно, где N пробелов а где N+1… Там еще подобная вещь есть — приоритет операций в зависимости от количества пробелов перед символами операций, ИМХО вообще безобразие. Также мне не понравилась система приоритетов операций, странные операции специально для беззнаковых чисел. До метапрограммирования еще не добрался Как я понимаю, в данной статье нет примеров обращения к API компилятора, а это самое «особенное» в Nim.


    1. frol Автор
      17.05.2015 21:40
      +5

      На счёт синтаксиса на пробелах — это киллер фича. Я не понимаю зачем дублировать определение блоков скобочками. Используйте отступ в 4 пробела и проблем не будет:

      class BankAccount(object):
      
          def __init__(self, initial_balance=0):
              self.balance = initial_balance
      
          def deposit(self, amount):
              self.balance += amount
      
          def withdraw(self, amount):
              self.balance -= amount
      
          def overdrawn(self):
              return self.balance < 0
      
      
      my_account = BankAccount(15)
      my_account.withdraw(5)
      print my_account.balance
      

      Неужели вам хочется видеть:

      class BankAccount(object):
      {
          def __init__(self, initial_balance=0):
          {
              self.balance = initial_balance
          }
      
          def deposit(self, amount):
          {
              self.balance += amount
          }
      
          def withdraw(self, amount):
          {
              self.balance -= amount
          }
      
          def overdrawn(self):
          {
              return self.balance < 0
          }
      }
      
      my_account = BankAccount(15)
      my_account.withdraw(5)
      print my_account.balance
      

      Правда хочется скобочек?

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

      Статья и так объёмная, да и документация немаленькая, всю не переведёшь сразу. К тому же, мне хватило пока макросов и шаблонов.


      1. NeoCode
        17.05.2015 22:04
        +6

        Да, мне хочется скобочек:) Я когда-то давно столкнулся с каким-то древним скриптом (на помню точно что это было), все написано правильно, а оно не работает… целый день на это убил. Оказалось, что там где-то нужно было пробелы вместо табуляции, то ли наоборот — нельзя было пробел ставить в начале строки…
        Пробелы невидимы, и это их главная проблема. А скобки задают структуру программы, они видимы, осязаемы.


        1. frol Автор
          17.05.2015 22:17
          +10

          Ваша проблема связана с плохой обработкой ошибки тем ЯП, а не пробелами-отступами. Например, я вот взял сейчас код на C и удалил одну закрывающуюся скобку, я думаю вы знаете, что компилятор меня отправил в конец файла и вывалил совершенно неадекватную ошибку: error: expected declaration or statement at end of input

          В то же время, я взял Python код и убрал один пробел или заменил пробелы в одном месте на табы и Python указал ровно ту строку, где у меня ошибка с вот такой понятной ошибкой: IndentationError: unexpected indent


      1. nick0x01
        17.05.2015 22:05
        +3

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


        1. frol Автор
          17.05.2015 22:12
          +1

          Зачем доводить до абсурда?


          1. bormotov
            17.05.2015 22:47
            +1

            почему же до абсурда? Дело вкуса и привычки, по большей части


          1. nick0x01
            17.05.2015 23:06

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

            foo("bar") -- замест этого
            foo "bar" -- писать так
            -- или
            foo({a=1, b=2})
            foo {a=1, b=2}
            


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

            foo(1, "bar);
            
            foo 1 "bar" // проще и понятнее выглядит
            


            1. frol Автор
              17.05.2015 23:20

              Кстати, в Nim скобки для аргументов функций тоже опциональны (тогда запятые ставить не нужно): nim-lang.org/docs/manual.html#procedures-command-invocation-syntax


              1. nick0x01
                18.05.2015 11:27

                О, здорово! Спасибо за ссылку.


            1. kmike
              17.05.2015 23:55
              +6

              Ну недостаток в том, что если у функции 0 аргументов, то не понятно, результат — это вызов функции или сама функция как объект.

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

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


              1. ApeCoder
                18.05.2015 00:44

                В Haskell и F# для этого и есть скобки (если непонятки с приоритетами) типа:

                let x = fun1 2 (3 + 5)


                1. Black_millenium
                  18.05.2015 02:45
                  +1

                  К примеру, я могу _догадаться_, что тут функции fun1 передаются два аргумента, 2 и 3+5, но математически оно не слишком очевидно. Программирование — это математика, что во многих функциональных и декларативных языках успешно игнорируется в плане читаемости.


                  1. nick0x01
                    18.05.2015 11:38

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

                    fun1 :: Int -> Int -> Int -- принимает 2 Int'а, результат тоже Int
                    

                    Ее можно частично применить и получить новую ф-ию:
                    let fun1' = fun1 2
                    

                    Получим новую ф-ию fun1' :: Int -> Int
                    С выделением арг-ов в скобки все это не очень удобно делать.
                    Можно использовав аппликатор ф-ий записать так:
                    let x = fun1 2 $ 3 + 5
                    


                  1. ApeCoder
                    18.05.2015 12:59
                    +1

                    > К примеру, я могу _догадаться_, что тут функции fun1 передаются два аргумента, 2 и 3+5, но математически оно не слишком очевидно.

                    А так вы любой язык с ходу понимаете. Главное чтобы скобочки на месте были?

                    Вообще, это дело привычки такие правила простые и учатся быстро :)


                  1. Yuuri
                    27.05.2015 15:52

                    Если уж говорить о математике, то там бесскобочная запись по крайней мере у унарных функций (sin x) сплошь и рядом. Да и если кого и обвинять в нематематичности, то уж точно не функциональщину ;)


              1. theoden
                18.05.2015 03:54
                +1

                Михаил, спасибо за комментарий.

                Если кратко, за бездумный поэтри моуд, «чувак, здесь можно без скобочек писать!», хочется убивать. Теперь конструктивно.

                1. Если скобки при вызове убрать, помимо упомянутых выше проблем теряется визуальный шаблон «слово(», используя который, можно скользить по тексту. Т.е. падает читаемость.
                2. И если предыдущий пункт теоретически можно скомпенсировать подсветкой синтаксиса, то запятая в качестве разделителя — слишком мощный визуальный шаблон, чтобы им поступиться. Пробелов явно недостаточно для разделения — посмотрите на пример выше. Ими часто отбиваются знаки арифметических действий, и чтобы понять: «Этот пробел разделяет аргументы или являются частью аргумента?», нужно вчитываться. В случае использования запятых достаточно беглого взгляда.

                Подводя итог, к общему правилу
                все эти «если .., то код значит вот то, но если .., то код значит вот это» усложняют язык
                хочется добавить:

                Убирать дублирующиеся границы — хорошо, убирать все — это именно доводить до абсурда.

                Всем читаемого и поддерживаемого кода.


              1. nick0x01
                18.05.2015 11:55

                Ну недостаток в том, что если у функции 0 аргументов, то не понятно, результат — это вызов функции или сама функция как объект.

                Да, функциональный стиль немного расслабил (пишу на Haskell). Это же разруливается типом функций.
                Пример на Haskell, про «sin x»:
                foo $ sin x
                -- или
                foo (sin x)
                

                вместо
                foo(sin(x));
                

                Выше $ — принимает два аргумента — функцию и значение, которое передает в эту ф-ий. Он имеет самый низкий приоритет, так что выражение «sin x» будет вычислено первым.


                1. ApeCoder
                  18.05.2015 13:01

                  если у функции 0 аргументов — это константа :)


                  1. capgelka
                    18.05.2015 13:13

                    Не всегда, что-нибудь в духе rand() выбивается из этого правила.


                    1. kstep
                      27.05.2015 18:37

                      Rand() оперирует внешним состоянием, так что это не чистая функция. Фактически в обычном императивном коде rand() неявно принимает некое состояние генератора случайных чисел извне. В функциональном коде тип этой функции будет наподобие Rand -> (Rand, float), то есть принимает некое состояние ГСЧ и возвращает новое состояние ГСЧ и случайно сгенерированное значение. Так что нет, это функция не от нуля параметров, а от одного (неявного) параметра.

                      Чем мне ещё нравится ФП, так это тем, что заставляет лучше видеть такие неявные параметры, к которым мы привыкли и на которые не обращаем внимание. Фактически состояние ГСЧ — это часть интерфейса rand(), просто обычно она опускается.


                  1. nick0x01
                    18.05.2015 13:15

                    Ну, да, если это чистая функция.


                    1. ApeCoder
                      18.05.2015 13:26

                      В F# введен спецтип «ничего»

                      // Константа

                      let x = «y»

                      // функция

                      let x () = «y»


                      1. Yuuri
                        27.05.2015 15:56

                        В хаскеле он тоже есть. Зовётся эта штука обычно unit и формально, да и синтаксически, представляет собой всего лишь пустой (0-местный) кортеж. Неправильно говорить, что это именно «ничего», потому что это вполне себе значение вполне себе конкретного типа, и есть настоящие пустые типы совсем без значений.


        1. NeoCode
          17.05.2015 22:41
          +4

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


          1. bormotov
            17.05.2015 22:56

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

            Инфиксная запись ближе к традиционному курсу математики, сколько уже сотен лет.

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

            Думаю, что ответ на вопрос «почему не перешли массово в индустрии», больше связан не с тем, в каком стиле записывают выражения, а с тем, сколько бабла в конкретный инструмент влито. Посмотрите, например, историю Явы. Когда она начинала выстреливать, уже вполне себе был Оберон, и апплеты на Jiuce работали в тогдашнем Нетскейпе, и что там еще из браузеров было. Вот только на стороне Sun внезапно начали играть IBM и Oracle.

            Ситуация не уникальная — посмотрите любую борьбу стандартов, и кто выигрывает.


          1. nick0x01
            17.05.2015 23:16

            Вообще, возможность использовать инфиксную запись для ф-ий с двумя аргументами — очень клевая штука. То есть писать вызов функций как в префиксной, так и инфиксной (с использованием символа-модификатора), в зависимости от необходимости. Позволяет сделать код более читаемым.
            Про переменные записанные в ряд, вы меня в тупик поставили. Можете пример привести, где такое используется? Пришло в голову объявление/определение нескольких переменных одного типа в c++, но там используется оператор запятая.


            1. NeoCode
              18.05.2015 00:17
              +1

              Даже если нигде и не используется — пунктуаторы делают код визуально более читаемым. А если код — просто набор идентификаторов-имен через пробелы, это плохо. Равно как и переизбыток скобок типа как в лиспе… Конечно, это чисто психологический момент, но всего должно быть в меру.

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

              foo(10,bar(20),baz(30,40)))

              Как вы такое запишете без скобок?


              1. sl_bug
                18.05.2015 00:31

                foo 10, bar(20), baz(30, 40)


              1. ApeCoder
                18.05.2015 00:46

                f#:

                foo 10 (bar 20) (baz 30 40)


              1. valplo
                18.05.2015 08:22

                10 20 bar 30 40 baz foo


                1. NeoCode
                  18.05.2015 09:21
                  +1

                  Как в этой записи визуально отличить к какой функции например относится число 10?
                  (10 (20 bar) (30 40 baz) foo)
                  или
                  ((10 20 bar) (30 40 baz) foo)


                  1. valplo
                    18.05.2015 11:17

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


                    1. NeoCode
                      18.05.2015 11:43
                      +1

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


              1. nick0x01
                18.05.2015 11:24

                Совсем без скобок нет, но с меньшим их кол-вом.
                Haskell:

                foo 10 (bar 20) (baz 30 40) -- так же как в f#
                foo 10 (bar 20) $ baz 30 40
                foo 10 (bar 20) $ 30 `baz` 40
                foo 10 (bar 20) . baz 30 $ 40
                


                1. Mingun
                  18.05.2015 16:12
                  +1

                  Мне кажется, что символы $,. (точка), `` используются для той же цели, что и скобки, причем, в последнем случае даже по количеству никакого выигрыша нет. И во всех случаях нужно держать в голове, что для вызова функции в таких-то случаях нужно поставить такой символ, а в таких можно не ставить. И зачем оно надо?


                  1. nick0x01
                    18.05.2015 16:56
                    +2

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

                    Оператор $ принимает первым аргументом функцию, вторым — значение, которое будет передано в ф-ию, и он имеет самый низкий приоритет, к тому же применение ф-ии посредством $ правоассоциативно. Получаем возможность такой записи:

                    bar $ 1 + 1
                    

                    Далее. Оператор (.) позволяет делать композицию функций, собственно и выглядит как в математике: (f. g)(x) = f(g(x)). Позволяет делать цепочки вызовов.
                    let twicebar = bar . bar
                    twicebar 1 -- вместо bar(bar(1))
                    -- тоже что и
                    bar $ bar 1
                    

                    В виду этого можно писать более понятный код. Например:
                    foo . bar $ baz 1  -- замест foo(bar(baz(1)))
                    


          1. ApeCoder
            17.05.2015 23:48

            Вам просто непривычно. Что такое «просто переменные записаны в ряд» если такой конструкции в языке нет.

            В F# например практически все вызов функции — попробуйте прочитать примеры на tryfsharp.org


  1. kemsky
    17.05.2015 22:41
    +3

    Не совсем понятна цель проекта. Сам принцип ооочень похож на Purebasic, там исходники выгоняются в асм и компилятся fasm-ом, так же есть кроссплатформенный сдк, нативный UI на каждой ОС, автоматический импорт API ОС, макросы, модули и тп, но он процедурный (можно конечно объекты сделать руками или препроцессором).

    После общения с Purebasic (и теперь nim), я пришел к выводу, что не надо изобретить велосипед — аккуратная реализация ActionScript/TypeScript или даже Java синтаксиса — порвала бы всех и все за счет возможности использовать уже готовые библиотеки, а ООП позволило бы подключить что угодно в будущем. Nim же явно с уклоном на фичи собранные по всему миру(как и скала), но это не всегда хорошо, язык должен быть простым.


    1. frol Автор
      17.05.2015 22:52

      Согласен с вами. Цель смутная, но радует тот факт, что у языка всё равно есть сильные стороны (производительсность, опциональный GC и другое). Тем не менее, мне кажется, что свой синтаксис таки помогает решать ряд архитектурных проблем, да и многие из популярных статически типизированных языков проигрывают по лаконичности синтаксиса Nim, так что может оно и правильно.

      Однако, использование уже имеющихся модулей и наработок является очень здравой мыслью, поэтому, к счастью, есть такие проекты как Nuitka (LLVM компилятор Python кода), Pyston (полностью новая реализация Python 2 от команды Dropbox) и другие.


    1. dannote
      18.05.2015 02:48

      Куда больше напоминает Genie. Он и Vala промежуточно компилируются в код на C, генерируя при этом весь boilerplate-код для GObject.


  1. JagaJaga
    17.05.2015 23:45
    +1

    Синтаксис какой-то слишком… Сложный.

    Ну, а про вычисление во время компиляции — template haskell, не? :)


    1. frol Автор
      17.05.2015 23:51
      +2

      Вы называете Nim сложным и тут же в противовес выдвигаете Haskell? У меня нет опыта с Haskell, но мне кажется, что он посложнее будет. Кроме того, Nim показывается 3.75х лучшую производительность, чем Haskell на представленном синтетическом тесте, так что у него будет своя ниша, я уверен.


      1. JagaJaga
        17.05.2015 23:55

        Не так страшен звеерь, как его малююют :)

        Интересно про тесты, тк haskell может показывать производительность не столь печальную даже для сравнений с C. Просто нужно уметь писать, всякие фьюжн деревья, например…


        1. frol Автор
          17.05.2015 23:58

          В статье есть ссылка на benchmark'и, но вот повторю её здесь: github.com/logicchains/LPATHBench/blob/master/writeup.md


  1. impwx
    18.05.2015 00:46

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


    1. valplo
      18.05.2015 12:02
      +1

      Ага, #define true false отдыхает. nim-lang.org/docs/manual.html#special-operators-dot-operators


  1. vintage
    19.05.2015 00:51
    +2

    Отступы — это классно. Функционал не плох, но всё же простоват. Давайте сравним с D…

    template times(x: expr, y: stmt): stmt =
      for i in 1..x:
        y
    
    10.times:
      echo "Hello World"
    

    Эквивалент на D:
    import std.stdio;
    
    void times( T )( T x , void delegate() y )
    {
    	for( T i = 0 ; i < x ; ++i ) {
    		y();
    	}
    }
    
    void main()
    {
    	10.times({
    		writeln( "Hello World" );
    	});
    }
    

    Оба языка позволяют вызывать функции как методы, однако D позволяет более чётко описать контракт функции.



    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))
    

    Эквивалент на D:
    import std.stdio;
    
    void main()
    {
    	auto seq2D = new bool[10][20];
    	writeln( seq2D[19][9] );
    }
    

    D имеет более продуманный синтаксис работы с массивами и словарями.



    В первом шаблоне мы указываем, что a * 2 может быть заменено на a + a.

    Почему не на «a << 1»? Тем не менее в любом другом современном языке такие простые оптимизации компилятор делает самостоятельно. А если не делает — лучше всего помочь сделать сам компилятор умнее, чем вставлять в код своего приложения костыли для компилятора.



    proc printf(formatstr: cstring)
      {.header: "<stdio.h>", varargs.}
    printf("%s %d\n", "foo", 5)
    

    Эквивалент на D:
    extern(C) int printf( in char* format , ... );
    
    extern(C++) void main()
    {
    	printf( "%s %d\n" , "foo".ptr , 5 );
    }
    

    D кроме соглашения о вызовах C позволяет использовать также и соглашения C++, Windows и Pascal. Да и синтаксис не выглядит чужеродной мантрой.

    Продолжение может быть следует, но не сегодня :-)


  1. stas3k
    14.06.2015 09:30

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

    nim-lang.org/docs/tut1.html
    nim-lang.org/docs/tut2.html


    1. frol Автор
      14.06.2015 09:41
      +1

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


      1. stas3k
        14.06.2015 09:59

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