Пользователь
0,0
рейтинг
17 мая 2015 в 12:42

Разработка → Что такого особенного в Nim? перевод



Язык программирования 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, ваш код будет работать практически везде.
Перевод: Dennis Felsing
Владислав Фролов @frol
карма
44,7
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • +3
    Я познакомился с Nim вчера утром после прочтения статьи о Rust 1.0 (спасибо kstep), после прочтения которой я заинтересовался как о нём отзываются в сравнении с Go, D и прочими, вот так я наткнулся среди прочих на ЯП Nim после чего провёл практически целый день с ним :)
    • 0
      Выглядит вкусно. А что на нём уже написано?
      • +1
        Самое главное, что уже написан пакетный менеджер Nimble)))

        Вот список пакетов — github.com/nim-lang/packages/blob/master/packages.json
        • 0
          Нет, я имею в виду — какой существующий и работающий софт уже написан на Nim?
        • 0
          С каждым новым языком жду, когда появится проект унифицированного пакетного менеджера, чтобы не ставить все эти npm, pip, nimble, cargo и прочий легион нежити и не запоминать по стопитьсот команд каждого из них.
          • +4
            image
          • 0
            Вон в дистрибутивах Линуксов есть унифицированные пакетные менеджеры. Но, похоже, разработчикам языка легче написать свой пакетный менеджер (заодно получив второй крупный проект для обкатки своего языка), чем бодаться с бюрократией пачки дистрибутивов. Да и не думаю, что пакетные дистрибутивы будут рады десяткам и сотням тысяч новых пакетов в них. И не дай бог пакетный менеджер не подойдёт (например, потому что не умеет держать несколько версий пакета в системе).
            • 0
              Ну так проблема поставлена, цели определены — что мешает их учесть и начать хотя бы процесс сближения разных менеджеров?

              Да и не думаю, что пакетные дистрибутивы будут рады десяткам и сотням тысяч новых пакетов в них

              Здрасте, а нахрена тогда этот менеджер? Наличие одного менеджера не означает ведь, что все дистрибутивы должны включать в себя какие-то пакеты. Это значит только, что написав одну строчку в конфигурационном файле, один пакетный менеджер теперь может ставить модули для node.js, написав вторую — модули для python, третью — для nim и т.д.
          • 0
            Мне кажется естественным использование разных пакетных менеджеров для решения разных задач — ведь тяжело придумать ситуацию, при которой человек сходу не сможет определить какой конкертно менеджер ему использовать.
            К тому же, часть менеджеров platform-specific, npm, например ставит всё в директорию, а pip должен иметь две версии (pip2 и pip3).
            Тем более, команды у них, как правило, одинаковые: install, update, etc. А если что — man npm — дело 10 секунд :)
            • 0
              Ну в таком случае также должно быть естественным делать make; make install — команды, как правило, одинаковые. Однако, боюсь, адепты пакетных менеджеров закидают какашками :)
              • 0
                Не надо путать мягкое с горячим. Make — это система сборки, а не пакетный менеджер. Тогда уже надо вспомнить rake, cake, *make (cmake, imake, ...), ant, grunt и иже с ними, тысячи их!
  • 0
    Я джва года ждал такой язык. А насколько он привязан к рантайму? В смысле, без кучи работать может?
    • 0
      Я не очень разбираюсь в этом нюансе. Подскажите, пожалуйста, чем куча плоха?

      Вот приведу цитату с сайта 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
        Если я, например, хочу написать ядро ОС, куча мне будет совсем не в тему. Можно, конечно, наваять простенький аллокатор на ассемблере…
      • +3
        Использование кучи требует аллокатора. Если использование кучи не отключается, то некоторые системные вещи написать не получиться, например ядро ОС, низкоуровневые драйвера/фёрмварь, прошивки (embedded software).
        • +1
          Я задал вопрос про отключение кучи на github: github.com/Araq/Nim/issues/2746
          • +9
            Ха, намек хороший в ответе, не знаю — заметил ты или нет. Ты задаешь вопрос, типа читатели _моей_статьи интересуются. А он отвечает, ой, как здорово, спасибо за первод _моей_ статьи, рад, что у нее так много читателей :-р
            • +1
              Так статья-то, вроде как, перевод? Вон, и ссылка стоит на оригинал; я вам больше скажу, из оригинала сюда теперь тоже стоит ссылка.
              • +2
                спасибо, Кэп
    • 0
      Может. Вот тут можно прочитать про это nim-lang.org/docs/manual.html#types-reference-and-pointer-types
      • 0
        Ой, извиняюсь, я думал вы про сборщик спрашивали.
    • 0
      Rust может.
      • –1
        У руста переусложненный механизм работы с памятью, это факт. В областях видимости сам черт ногу сломит.
        • +1
          Я бы сказал что он строгий, но не переусложнённый, в нем все довольно таки четко и понятно.
          В результате расту не нужен GC.
  • +1
    Может быть участники Nim сообщества здесь подскажут относительно concurrency model, async IO. Текущее положение и тренд разработки? Я задавал вопросы на IRC канале с полгода назад и получил ответ — «Мы работаем над этим». На ту пору разработчики склонялись в пользу native OS threads против user-space threads и библиотечных вызовов против специального синтаксиса. Как обстоят дела сейчас, где почитать?
    • –2
      Код на картинке в начале статьи посмотреть.
  • +5
    Язык сильно перегружен фичами, синтаксис сильно сильно вольный. Это может привести его к фейлу, хотя и не обязательно. Но так, красный флажок точно есть. Это усложнит его изучение и затруднит разбор чужого кода.

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

    Не критикую, но обратить внимание стоит на эти моменты.
    • 0
      Перефразировал предложение про призводительность. Действительно, дал маху. Спасибо, что обратили внимание.

      На счёт синтаксиса — да, мне тоже кажется, что слишком много фич, но я не берусь судить пока. Unified Call Syntax — это кошмар, я очень надеюсь на здравость рассудка людей чтобы это не попало в C++. При первом знакомстве мне показалось, что всё хорошо, но когда я начал копать примеры и проекты на Nim, я понял, что чтение у меня вызывает трудности сравнимые с Ruby. Python для меня является образцом, но и там впиливают всё больше магии с asyncio прямо в синтаксис и меня это расстраивает.
      • 0
        Опа. А я смотрю приложенный код, и у меня стойкая аналогия с Ruby. Смотрел бы «по диагонали», вообще решил бы, что что-то рубиновое описывают в статье. :) Похоже не только у меня такое ощущение. :)
        • 0
          Я даже в этой публикации использовал подсветку синтаксиса от ruby :)
  • +3
    Заголовок статьи не соответсвует содержанию. По содержанию получается ответ «ничего такого особенного».
    • +3
      Во-первых, заголовок я сохранил авторский.
      Во-вторых, я бы назвал ключевые особенности такого рода: производительность программ на уровне С/С++ при «интересном» синтаксисе и возможностях языка, возможность транслирования кода в C/C++/JS.
    • +2
      Да ладно вам. Первый приличный язык «поверх C» с нормальным метапрограммированием (как в LISP'е, когда метапрограммирование и программирование не являются двумя языками, любой модуль можно спокойно использовать во время компиляции).

      Хотя вопросов много. Как там с поддержкой больших проектов? Кросс-компиляцией? Как это вообще реализовано «внутри»? Кто-нибудь смотрел?
      • 0
        мало кто помнит, что Страуструп тоже начал с трансляции в C, называлось это cfront
  • +5
    Язык интересный, как раз читаю неспеша документацию, отмечаю интересные моменты (как положительные так и отрицательные). Фич никогда много не бывает, но вот синтаксис на отступах мне категорически не нравится — слишком это неочевидно, где N пробелов а где N+1… Там еще подобная вещь есть — приоритет операций в зависимости от количества пробелов перед символами операций, ИМХО вообще безобразие. Также мне не понравилась система приоритетов операций, странные операции специально для беззнаковых чисел. До метапрограммирования еще не добрался Как я понимаю, в данной статье нет примеров обращения к API компилятора, а это самое «особенное» в Nim.
    • +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
      

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

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

      Статья и так объёмная, да и документация немаленькая, всю не переведёшь сразу. К тому же, мне хватило пока макросов и шаблонов.
      • +6
        Да, мне хочется скобочек:) Я когда-то давно столкнулся с каким-то древним скриптом (на помню точно что это было), все написано правильно, а оно не работает… целый день на это убил. Оказалось, что там где-то нужно было пробелы вместо табуляции, то ли наоборот — нельзя было пробел ставить в начале строки…
        Пробелы невидимы, и это их главная проблема. А скобки задают структуру программы, они видимы, осязаемы.
        • +10
          Ваша проблема связана с плохой обработкой ошибки тем ЯП, а не пробелами-отступами. Например, я вот взял сейчас код на C и удалил одну закрывающуюся скобку, я думаю вы знаете, что компилятор меня отправил в конец файла и вывалил совершенно неадекватную ошибку: error: expected declaration or statement at end of input

          В то же время, я взял Python код и убрал один пробел или заменил пробелы в одном месте на табы и Python указал ровно ту строку, где у меня ошибка с вот такой понятной ошибкой: IndentationError: unexpected indent
      • +3
        А еще бессмысленны скобки вокруг аргументов ф-ий и разделители (запятые) между ними, есть к чему стремиться синтаксису этого языка.
        • +1
          Зачем доводить до абсурда?
          • +1
            почему же до абсурда? Дело вкуса и привычки, по большей части
          • 0
            Нисколько не абсурд, что вы. Думаю, по большому счету, фигурные скобки для блоков несут больший смысл, чем круглые скобки и замятые для аргументов. Разве что проще распарсить исходный код.
            Мне эта идея понравилась, когда еще приходилось писать некоторый код на lua, интерпретатор позволял не писать скобки при вызове ф-ий с одним аргументов типа строка или таблица. Выглядело так:

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


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

            foo(1, "bar);
            
            foo 1 "bar" // проще и понятнее выглядит
            
            • 0
              Кстати, в Nim скобки для аргументов функций тоже опциональны (тогда запятые ставить не нужно): nim-lang.org/docs/manual.html#procedures-command-invocation-syntax
              • 0
                О, здорово! Спасибо за ссылку.
            • +6
              Ну недостаток в том, что если у функции 0 аргументов, то не понятно, результат — это вызов функции или сама функция как объект.

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

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

                let x = fun1 2 (3 + 5)
                • НЛО прилетело и опубликовало эту надпись здесь
                  • 0
                    Дело в том, что в Haskell есть частичной применение ф-ий, каррирование, композиция ф-ий. И передача аргументов в скобках мешает записи таких выражений. Например, если взять пример ApeCoder:
                    тип fun1 будет, допустим
                    fun1 :: Int -> Int -> Int -- принимает 2 Int'а, результат тоже Int
                    

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

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

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

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

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

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

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

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

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

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

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

                Выше $ — принимает два аргумента — функцию и значение, которое передает в эту ф-ий. Он имеет самый низкий приоритет, так что выражение «sin x» будет вычислено первым.
                • 0
                  если у функции 0 аргументов — это константа :)
                  • 0
                    Не всегда, что-нибудь в духе rand() выбивается из этого правила.
                    • 0
                      Rand() оперирует внешним состоянием, так что это не чистая функция. Фактически в обычном императивном коде rand() неявно принимает некое состояние генератора случайных чисел извне. В функциональном коде тип этой функции будет наподобие Rand -> (Rand, float), то есть принимает некое состояние ГСЧ и возвращает новое состояние ГСЧ и случайно сгенерированное значение. Так что нет, это функция не от нуля параметров, а от одного (неявного) параметра.

                      Чем мне ещё нравится ФП, так это тем, что заставляет лучше видеть такие неявные параметры, к которым мы привыкли и на которые не обращаем внимание. Фактически состояние ГСЧ — это часть интерфейса rand(), просто обычно она опускается.
                  • 0
                    Ну, да, если это чистая функция.
                    • 0
                      В F# введен спецтип «ничего»

                      // Константа

                      let x = «y»

                      // функция

                      let x () = «y»
                      • 0
                        В хаскеле он тоже есть. Зовётся эта штука обычно unit и формально, да и синтаксически, представляет собой всего лишь пустой (0-местный) кортеж. Неправильно говорить, что это именно «ничего», потому что это вполне себе значение вполне себе конкретного типа, и есть настоящие пустые типы совсем без значений.
        • +4
          Ну так префиксная/постфиксная запись выражений тоже куда как оптимальнее инфиксной, никаких приоритетов не надо. Чего же мы на нее не перешли?
          Нет, на самом деле скобки вокруг аргументов функций играют важную роль — они выделяют то, что в данном месте именно вызов функции, а не просто переменные записаны в ряд:)
          • 0
            «почему не перешли», каждый ответит для себя сам.

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

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

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

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

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

              А вообще, допустим вы передаете результат одной функции в другую.
              foo(10,bar(20),baz(30,40)))

              Как вы такое запишете без скобок?
              • 0
                foo 10, bar(20), baz(30, 40)
              • 0
                f#:

                foo 10 (bar 20) (baz 30 40)
              • 0
                10 20 bar 30 40 baz foo
                • +1
                  Как в этой записи визуально отличить к какой функции например относится число 10?
                  (10 (20 bar) (30 40 baz) foo)
                  или
                  ((10 20 bar) (30 40 baz) foo)
                  • 0
                    В этой записи не допускается функции с переменным числом аргументов, поэтому неоднозначности не возникает.
                    • +1
                      Дело даже не в том, что в этой записи не допускается переменное число аргументов или перегрузка функций по аргументам (хотя и это не есть хорошо). А в том, что у меня допустим есть проект на несколько гигабайт исходников, в нем сотни тысяч функций, а я этот код первый раз вижу. И мне нужно как-то определить, какой аргумент к какой функции относится.
              • 0
                Совсем без скобок нет, но с меньшим их кол-вом.
                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
                  Мне кажется, что символы $,. (точка), `` используются для той же цели, что и скобки, причем, в последнем случае даже по количеству никакого выигрыша нет. И во всех случаях нужно держать в голове, что для вызова функции в таких-то случаях нужно поставить такой символ, а в таких можно не ставить. И зачем оно надо?
                  • +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)))
                    
          • 0
            Вам просто непривычно. Что такое «просто переменные записаны в ряд» если такой конструкции в языке нет.

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

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

      Однако, использование уже имеющихся модулей и наработок является очень здравой мыслью, поэтому, к счастью, есть такие проекты как Nuitka (LLVM компилятор Python кода), Pyston (полностью новая реализация Python 2 от команды Dropbox) и другие.
    • 0
      Куда больше напоминает Genie. Он и Vala промежуточно компилируются в код на C, генерируя при этом весь boilerplate-код для GObject.
      • +2
        Я только добрался посмотреть на Genie и Vala. Чёрт побери, нет предела совершенству! Я как раз думал, что Nim и Rust имеют свои сильные стороны и было бы интересно их скрестить и тут я читаю про Genie… Я пока прочитал только официальную вики-статью wiki.gnome.org/Projects/Genie, но может вы меня отговорите до того как я начну раскопки? Может он устарел или его забросили (Vala вроде релиз был недавно), или может он как-то к Gnome слишком привязан?
        • 0
          может он как-то к Gnome слишком привязан?
          Примерно как Objective C и MacOSX/iOS: теоретически язык вроде как вполне отдельный и универсальный, можно использовать где угодно, но практически — вне GNU/Linux'а это использовать тяжело.

          Заметьте: не вне GNOME, а вне GNU/Linux'а. GObject и прочее сейчас в результате используются не только в GNOME, но и в XFCE/KDE/etc.

          Но вот на других операционках (в том числе в Android'е несмотря на общее ядро) — эта вся конструкция оказывается сильно исскусственной и неудобной…
        • +1
          Отговаривать не буду. Он во всем хорош, есть только одно «но» — пока что сам синтаксис не вполне стабильный, планируется еще несколько дополнений. Если не смущает завязка на GObject и есть опыт работы с glib, вполне можно использовать для чего-то серьезного. С ним очень удобно работать в связке с GTK+ под *nix и binding-и (VAPI) легко делаются подо что угодно.
  • +1
    Синтаксис какой-то слишком… Сложный.

    Ну, а про вычисление во время компиляции — template haskell, не? :)
    • +2
      Вы называете Nim сложным и тут же в противовес выдвигаете Haskell? У меня нет опыта с Haskell, но мне кажется, что он посложнее будет. Кроме того, Nim показывается 3.75х лучшую производительность, чем Haskell на представленном синтетическом тесте, так что у него будет своя ниша, я уверен.
      • 0
        Не так страшен звеерь, как его малююют :)

        Интересно про тесты, тк haskell может показывать производительность не столь печальную даже для сравнений с C. Просто нужно уметь писать, всякие фьюжн деревья, например…
  • 0
    Язык интересный, захотелось посмотреть повнимательнее. Особенно интересная фишка насчет «оптимизаций» компилятора — не думаю, что именно в таком виде она широко применима, но как вариант метапрограммирования — довольно необычно.
    • +1
      Ага, #define true false отдыхает. nim-lang.org/docs/manual.html#special-operators-dot-operators
  • +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. Да и синтаксис не выглядит чужеродной мантрой.

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

    nim-lang.org/docs/tut1.html
    nim-lang.org/docs/tut2.html
    • +1
      По-моему, в этих туториалах достаточно мало текста и он предельно простой и даже можно просто сам код читать. Я перевёл эту статью, так как она показывает разносторонность языка и чем он может заинтересовать.
      • 0
        Может быть вы и правы, хотя не у всех просто с английским :) Будем ждать новых интересных статей по этому языку. Спасибо.
    • 0
      Кстати, если Вы вдруг пропустили переводы этих туториалов на хабре, то вот ссылки:

      habrahabr.ru/post/271197
      habrahabr.ru/post/271361

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