“Der Mensch ist doch ein Augentier – schone Dinge wunsch ich mir.”(Цитата из песни «Morgenstern» группы «Rammstein». Примерный перевод: «Но человек – глазастый зверь, – мне нужно множество красивых вещей».)
# Это комментарий
echo("What's your name? ")
var name: string = readLine(stdin)
echo("Hi, ", name, "!")
greetings.nim
. Теперь скомпилируйте и запустите его:nim compile --run greetings.nim
--run
заставляет Nim автоматически запустить файл после компиляции. Вы можете передать своей программе аргументы через командную строку, добавив их после имени файла:nim compile --run greetings.nim arg1 arg2
nim c -r greetings.nim
nim c -d:release greetings.nim
-d:release
отключает эти проверки и включает оптимизацию.var
объявляет новую переменную с именем name
и типом string
, после чего ей присваивается значение, которое вернула процедура readLine
. Поскольку компилятор знает, что readLine
возвращает строку, вы можете не писать тип в объявлении (это называется локальным выведением типов). Так что такой вариант тоже будет работать:var name = readLine(stdin)
echo
, readLine
и т.д. Эти встроенные команды объявлены в модуле system
, который неявно импортируется любым другим модулем.\
: \n
означает перевод строки, \t
– табуляцию и так далее. Также бывают сырые (raw) строковые литералы:r"C:\program files\nim"
""" ... """
, могут содержать перевод строки и \
в них не является экранирующим символом. Они очень полезны, например, для включения в код HTML-фрагментов.#
. Документирующие комментарии начинаются с ##
:# Комментарий.
var myVariable: int ## документирующий комментарий
discard
совместно с длинными строковыми литералами для создания блочных комментариев:discard """ Здесь можно разместить текст любого кода
на Nim без каких-либо ограничений по отступам.
yes("May I ask a pointless question?") """
1_000_000
(один миллион). Числа, содержащие точку (или e
, или E
) считаются литералами чисел с плавающей запятой: 1.0e9
(один миллиард). Шестнадцатеричные литералы начинаются с префикса 0x
, двоичные – с 0b
, а восьмеричные – с 0o
. Ведущий ноль не превращает число в восьмеричное.var
var
объявляет новую локальную или глобальную переменную:var x, y: int # объявляем x и y, имеющие тип `int`
var
можно перечислить целую секцию переменных:var
x, y: int
# здесь может быть комментарий
a, b, c: string
var x = "abc" # вводит новую переменную `x` и присваивает ей значение
x = "xyz" # присваивает новое значение `x`
=
это оператор присваивания. Он не может быть перегружен, переписан или запрещён, но это может измениться в будущих версиях Nim. Вы можете объявить несколько переменных одним оператором присваивания и все они получат одно и то же значение:var x, y = 3 # присваивает переменным `x` и `y` значение 3
echo "x ", x # выведет "x 3"
echo "y ", y # выведет "y 3"
x = 42 # изменяет значение `x` на 42, не меняя `y`
echo "x ", x # выведет "x 42"
echo "y ", y # выведет "y 3"
const x = "abc" # константа x содержит строку "abc"
const
можно перечислить целую секцию констант:const
x = 1
# комментарий, который может здесь присутствовать
y = 2
z = y + 5 # вычисления возможны
let
let
работает примерно как var
, но объявляет переменные однократного присваивания: после инициализации их значение не может быть изменено.let x = "abc" # вводит новую переменную `x` и присваивает ей значение
x = "xyz" # не сработает: присваивание `x`
let
и const
следующее: let
вводит переменную, которая не может быть переприсвоена, а const
означает «принудительно вычислить во время компиляции и поместить результат в секцию данных»:const input = readLine(stdin) # Ошибка: предполагается константное выражение
let input = readLine(stdin) # а это сработает
if
if
– один из способов организовать ветвление потока выполнения:let name = readLine(stdin)
if name == "":
echo("Poor soul, you lost your name?")
elif name == "name":
echo("Very funny, your name is name.")
else:
echo("Hi, ", name, "!")
elif
может быть ноль и более, ветка else
не обязательна. Ключевое слово elif
является сокращением для else if
, чтобы не делать излишних отступов. (""
это пустая строка, она не содержит символов.)case
case
. Она разделяет поток выполнения на несколько веток:let name = readLine(stdin)
case name
of "":
echo("Poor soul, you lost your name?")
of "name":
echo("Very funny, your name is name.")
of "Dave", "Frank":
echo("Cool name!")
else:
echo("Hi, ", name, "!")
of
можно использовать список значений, разделённых запятыми.case
может работать с целыми числами, другими перечислимыми типами и строками. (Что такое перечислимые типы будет рассказано позже.) Для целых чисел и перечислимых типов можно использовать диапазоны значений:# эта команда будет объясняться позже:
from strutils import parseInt
echo("A number please: ")
let n = parseInt(readLine(stdin))
case n
of 0..2, 4..7: echo("The number is in the set: {0, 1, 2, 4, 5, 6, 7}")
of 3, 8: echo("The number is 3 or 8")
n
, а код обрабатывает только значения 0..8
. Поскольку не слишком практично перечислять все возможные целые числа (хотя такое и возможно, благодаря нотации диапазонов), мы исправим это, указав компилятору, что для всех остальных значений ничего делать не нужно:...
case n
of 0..2, 4..7: echo("The number is in the set: {0, 1, 2, 4, 5, 6, 7}")
of 3, 8: echo("The number is 3 or 8")
else: discard
discard
это команда ничего не делать. Компилятор знает, что команда case
с секцией else
охватывает все возможные варианты и, таким образом, ошибка исчезает. Обратите внимание, что все строковые значения покрыть невозможно: вот почему в случае строк ветка else
обязательна.case
используется для диапазонов типов или перечислений, где хорошо помогает то, что компилятор проверяет охват всех возможных значений.while
while
представляет собой простой цикл:echo("What's your name? ")
var name = readLine(stdin)
while name == "":
echo("Please tell me your name: ")
name = readLine(stdin)
# `var` отсутствует, поскольку здесь не объявляется новая переменная
while
использован, чтобы спрашивать у пользователя его имя до тех пор, пока он не нажмёт ENTER (т.е. не введёт пустую строку).for
for
реализует цикл по всем элементам итератора. Вот пример использования встроенного итератора countup
:echo("Считаем до десяти: ")
for i in countup(1, 10):
echo($i)
# --> На выходе 1 2 3 4 5 6 7 8 9 10 на разных строчках
$
преобразует целое число (int
) и многие другие типы в строку. Переменная i
неявно объявляется циклом for
и имеет тип int
, поскольку countup
возвращает именно этот тип. i
проходит по значениям 1, 2, .., 10. Каждое значение выводится с помощью echo
. Этот код делает то же самое:echo("Считаем до 10: ")
var i = 1
while i <= 10:
echo($i)
inc(i) # увеличиваем i на 1
# --> На выходе 1 2 3 4 5 6 7 8 9 10 на разных строчках
echo("Считаем от 10 до 1: ")
for i in countdown(10, 1):
echo($i)
# --> На выходе 10 9 8 7 6 5 4 3 2 1 на разных строчках
..
, который делает то же, что и countup
:for i in 1..10:
...
block
x
недоступна за пределами цикла:while false:
var x = "hi"
echo(x) # не работает
while
(for
) создаёт неявный блок. Идентификаторы видимы только внутри того блока, в котором они были объявлены. Команду block
можно использовать, чтобы открыть новый блок явно:block myblock:
var x = "hi"
echo(x) # тоже не работает
myblock
в примере) не обязательна.break
break
. Эта команда может прерывать блоки команд while
, for
или block
. Она выходит из ближайшего блока, если не задана метка блока, из которого надо выйти:block myblock:
echo("входим в блок")
while true:
echo("цикл")
break # покидаем цикл, но не блок
echo("мы всё ещё в блоке")
block myblock2:
echo("входим в блок")
while true:
echo("цикл")
break myblock2 # покидаем блок (и цикл)
echo("мы всё ещё в блоке")
continue
continue
немедленно переходит к следующей итерации:while true:
let x = readLine(stdin)
if x == "": continue
echo(x)
when
when system.hostOS == "windows":
echo("running on Windows!")
elif system.hostOS == "linux":
echo("running on Linux!")
elif system.hostOS == "macosx":
echo("running on Mac OS X!")
else:
echo("unknown operating system")
when
почти идентична команде if
, но есть некоторые различия:true
.when
полезна для написания платформенно-зависимого кода, по аналогии с конструкцией #ifdef
языка C.Примечание: Чтобы закомментировать большой кусок кода, часто бывает удобнее использовать вместо комментариев конструкцию when false:
. Её можно делать неоднократно вложенной.
return
, не могут содержать других команд. Сложные команды, такие как if
, when
, for
, while
могут содержать другие команды. Чтобы избежать неоднозначности сложные команды всегда пишутся с отступом, а простые – нет:# для одиночного присваивания отступ не нужен:
if x: x = false
# нужен отступ для вложенного if:
if x:
if y:
y = false
else:
y = true
# нужен отступ, потому что две команды соответствуют условию:
if x:
x = false
y = false
if thisIsaLongCondition() and
thisIsAnotherLongCondition(1,
2, 3, 4):
x = true
;
) вы можете использовать команды там, где разрешены только выражения:# вычисляем fac(4) на этапе компиляции:
const fac4 = (var x = 1; for i in 1..4: x *= i; x)
echo
и readLine
из примеров, нам понадобится концепция процедур. (В некоторых языках они называются методами или функциями.) В Nim новые процедуры определяются с помощью ключевого слова proc
:proc yes(question: string): bool =
echo(question, " (y/n)")
while true:
case readLine(stdin)
of "y", "Y", "yes", "Yes": return true
of "n", "N", "no", "No": return false
else: echo("Please be clear: yes or no")
if yes("Should I delete all your important files?"):
echo("I'm sorry Dave, I'm afraid I can't do that.")
else:
echo("I think you know what the problem is just as well as I do.")
yes
, которая задаёт пользователю вопрос и возвращает true
, если он ответил «yes», и false
, если он ответил «no». Команда return
приводит к немедленному выходу из процедуры (и, соответственно, цикла while
). Синтаксис (question: string): bool
означает, что процедура ожидает получить параметр с именем question
и типом string
и вернёт значение типа bool
. bool
это встроенный тип: единственные значения, которые он может принимать, это true
и false
. Условия в командах if
или while
должны иметь тип bool
.question
формально называется параметром, а "Should I..."
– аргументом, который передаётся в этом параметре.result
result
, которая представляет собой возвращаемое значение. Команда return
без аргументов это просто сокращение для return result
. Переменная result
всегда возвращается при выходе из процедуры, даже если команды return
не было.proc sumTillNegative(x: varargs[int]): int =
for i in x:
if i < 0:
return
result = result + i
echo sumTillNegative() # выведет 0
echo sumTillNegative(3, 4, 5) # выведет 12
echo sumTillNegative(3, 4 , -1 , 6) # выведет 7
result
всегда уже объявлена, так что попытка объявить её снова, например, с помощью var result
, приведёт к затенению её обычной переменной с тем же именем. Переменная result
всегда инициализируется значением по умолчанию для своего типа. Поэтому ссылочные типы данных будут иметь значение nil
, так что их при необходимости придётся инициализировать вручную.var
. Затенение имени параметра возможно и иногда используется:proc printSeq(s: seq, nprinted: int = -1) =
var nprinted = if nprinted == -1: s.len else: min(nprinted, s.len)
for i in 0 .. <nprinted:
echo s[i]
var
-параметр:proc divmod(a, b: int; res, remainder: var int) =
res = a div b # целочисленное деление
remainder = a mod b # целочисленное взятие остатка
var
x, y: int
echo(x)
divmod(8, 5, x, y) # модифицирует x и y
echo(y)
res
и remainder
являются var
-параметрами. Такие параметры могут модифицироваться процедурой и изменения станут видимы вызвавшему. Отметим, что в примере выше вместо var
-параметров лучше было бы вернуть кортеж.discard
discard
. В Nim нельзя просто взять и отбросить возвращённое значение:discard yes("Можно мне задать бесполезный вопрос?")
discardable
:proc p(x, y: int): int {.discardable.} =
return x + y
p(3, 4) # теперь работает
discard
также можно использовать для создания блока комментариев, как описано в разделе Комментарии.proc createWindow(x, y, width, height: int; title: string;
show: bool): Window =
...
var w = createWindow(show = true, title = "My Application",
x = 0, y = 0, height = 600, width = 800)
createWindow
, порядок аргументов больше не имеет значения. Можно смешивать именованные аргументы с неименованными, но это ухудшает читаемость:var w = createWindow(0, 0, title = "My Application",
height = 600, width = 800, true)
createWindow
стало проще использовать, она должна предоставлять значения по умолчанию, то есть те значения, которые будут использоваться в качестве аргументов, если вызвавший их не указал:proc createWindow(x = 0, y = 0, width = 500, height = 700,
title = "unknown",
show = true): Window =
...
var w = createWindow(title = "My Application", height = 600, width = 800)
createWindow
нужно указать лишь те значения, которые отличаются от значений по умолчанию.title: string = "unknown"
.proc toString(x: int): string = ...
proc toString(x: bool): string =
if x: result = "true"
else: result = "false"
echo(toString(13)) # вызывается процедура toString(x: int)
echo(toString(true)) # вызывается процедура toString(x: bool)
toString
в Nim обычно реализуется оператором $
.) Компилятор выберет наиболее подходящую процедуру для вызова toString
. Как именно работает алгоритм выбора перегружаемых процедур, тут обсуждаться не будет (этот вопрос будет скоро разобран в руководстве). Однако он не приводит к неприятным сюрпризам и базируется на довольно простом алгоритме унификации. Неоднозначные вызовы приводят к выводу сообщения об ошибке.+
это просто перегруженная процедура. Парсер позволяет использовать операторы в инфиксной (a + b)
или префиксной нотации (+ a)
. Инфиксный оператор всегда получает два аргумента, а префиксный – всегда один. Постфиксные операторы запрещены, поскольку могут привести к неоднозначности: a @ @ b
означает (a) @ (@b)
или (a@) @ (b)
? Поскольку постфиксных операторов в Nim нет, это выражение всегда будет означать (a) @ (@b)
.and
, or
и not
, операторы всегда состоят из таких символов: + - * \ / < > = @ $ ~ & % ! ? ^ . |
@!?+~
, но читаемость может пострадать.proc `$` (x: myDataType): string = ...
# теперь оператор $ также работает и с myDataType, перекрывая поведение $,
# реализованное ранее только для встроенных типов
if `==`( `+`(3, 4), 7): echo("True")
# предварительное объявление:
proc even(n: int): bool
proc even(n: int): bool
proc odd(n: int): bool =
assert(n >= 0) # гарантирует, что мы не свалимся в отрицательную рекурсию
if n == 0: false
else:
n == 1 or even(n-1)
proc even(n: int): bool =
assert(n >= 0) # гарантирует, что мы не свалимся в отрицательную рекурсию
if n == 1: false
else:
n == 0 or odd(n-1)
odd
зависит от even
и наоборот. Таким образом, even
должна встретиться компилятору до того, как будет полностью определена. Синтаксис для такого предварительного объявления несложен: просто пропустите =
и тело процедуры. assert
добавляет граничные условия и будет описан позднее в разделе Модули.echo("Считаем до десяти: ")
for i in countup(1, 10):
echo($i)
countup
для использования в таком цикле? Давайте попробуем:proc countup(a, b: int): int =
var res = a
while res <= b:
return res
inc(res)
yield
. Теперь осталось лишь заменить ключевое слово proc
на iterator
и вот он – наш первый итератор:iterator countup(a, b: int): int =
var res = a
while res <= b:
yield res
inc(res)
for
;return
, а процедуры не могут содержать команду yield
;result
;closure
, имеющий другой набор ограничений. Подробности смотрите в разделе документации «Итераторы первого класса». Итераторы могут иметь те же имена и параметры, что и процедуры: у них своё собственное пространство имён. Поэтому есть общепринятая практика оборачивания итераторов в процедуры с теми же именами, которые накапливают результат итератора и возвращают его в виде последовательности, как split
из модуля strutils
.bool
и состоит из двух предопределённых значений true
и false
. Условия в командах while
, if
, elif
и when
должны иметь тип bool
.bool
определены операторы not
, and
, or
, xor
, <
, <=
, >
, >=
, !=
и ==
. Операторы and
и or
выполняют сокращённые вычисления. Пример:while p != nil and p.name != "xyz":
# p.name не вычисляется, если p == nil
p = p.next
char
. Его размер равен одному байту. Таким образом, он не может представлять собой символ UTF-8, только его часть. Причина этого в эффективности: в подавляющем большинстве случаев готовые программы будут правильно обрабатывать данные в UTF-8, так как UTF-8 был разработан специально для этого. Символьные литералы заключаются в одиночные кавычки.==
, <
, <=
, >
и >=
. Оператор $
преобразует char
в string
. Символы не могут смешиваться с целыми числами; для получения численного значения символа используйте процедуру ord
. Конвертирование из численного значения в символ выполняется с помощью процедуры chr
.len
; длина никогда не учитывает завершающий ноль. Доступ к завершающему нолю не вызывает ошибки и часто приводит к упрощению кода:if s[i] == 'a' and s[i+1] == 'b':
# не нужно проверять, что i < len(s)!
...
&
для конкатенации строк и add
для добавления подстроки.s[i]
означает i
-й символ (а не i
-й юникодный символ) строки s
.nil
. Однако большинство строковых операций не могут работать с nil
(это приводит к возбуждению исключения) по соображениям производительности. Вместо nil
в качестве пустого значения нужно использовать пустую строку ""
. Но ""
создаёт строковый объект в куче, так что здесь нужно искать компромисс между памятью и производительностью.int
, int8
, int16
, int32
, int64
, uint
, uint8
, uint16
, uint32
и uint64
.int
. Целочисленные литералы могут иметь суффикс, обозначающий их принадлежность к тому или иному целочисленному типу:let
x = 0 # x имеет тип int
y = 0'i8 # y имеет тип int8
z = 0'i64 # z имеет тип int64
u = 0'u # u имеет тип uint
int
равен размеру указателя.+
, -
, *
, div
, mod
, <
, <=
, ==
, !=
, >
и >=
определены для целых чисел. Операторы and
, or
, xor
и not
также определены для целых чисел и выполняют побитовые операции. Битовый сдвиг влево делается с помощью оператора shl
, а сдвиг вправо – с помощью оператора shr
. Операторы битового сдвига всегда трактуют свой аргумент как беззнаковое число. Они могут использоваться для умножения или деления.EOutOfRange
(если ошибка не была обнаружена на этапе компиляции).float
, float32
и float64
.float
. В текущей реализации float
всегда имеет размер 64 бита.var
x = 0.0 # x имеет тип float
y = 0.0'f32 # y имеет тип float32
z = 0.0'f64 # z имеет тип float64
+
, -
, *
, /
, <
, <=
, ==
, !=
, >
и >=
определены для чисел с плавающей запятой и соответствуют стандарту IEEE.toInt
и toFloat
.var
x: int32 = 1.int32 # то же, что вызов int32(1)
y: int8 = int8('a') # 'a' == 97'i8
z: float = 2.5 # int(2.5) округляется до 2
sum: int = int(x) + int(y) + int(z) # sum == 100
$
превращает любой базовый тип в строку, которую вы можете вывести на экран с помощью процедуры echo
. Однако оператор $
не сможет работать со сложными типами или типами, которые вы создали самостоятельно, пока вы не переопределите его для них.$
. В таком случае можно воспользоваться процедурой repr
, которая работает с любыми типами и даже со сложными циклическими графами данных. Следующий пример показывает, что даже для базовых типов есть разница между результатами $
и repr
:var
myBool = true
myCharacter = 'n'
myString = "nim"
myInteger = 42
myFloat = 3.14
echo($myBool, ":", repr(myBool))
# --> true:true
echo($myCharacter, ":", repr(myCharacter))
# --> n:'n'
echo($myString, ":", repr(myString))
# --> nim:0x10fa8c050"nim"
echo($myInteger, ":", repr(myInteger))
# --> 42:42
echo($myFloat, ":", repr(myFloat))
# --> 3.1400000000000001e+00:3.1400000000000001e+00
type
:type
biggestInt = int64 # целочисленный тип, больший, чем это доступно
biggestFloat = float64 # тип с плавающей запятой, больший, чем это доступно
type
.type
Direction = enum
north, east, south, west
var x = south # `x` имеет тип `Direction`; его значение `south`
echo($x) # выводит "south" на `stdout`
Direction.south
.$
может конвертировать любое значение перечисления в его имя, а процедура ord
– в соответствующее целочисленное значение.type
MyEnum = enum
a = 2, b = 4, c = 89
char
, bool
(и их поддиапазоны) – все они называются перечислимыми типами. Для перечислимых типов есть несколько специальных операций:Операция | Комментарий |
---|---|
ord(x) |
возвращает целое число, которое используется для представления значения x |
inc(x) |
увеличивает x на 1 |
inc(x, n) |
увеличивает x на n ; n – целое число |
dec(x) |
уменьшает x на 1 |
dec(x, n) |
уменьшает x на n ; n – целое число |
succ(x) |
возвращает следующий за x элемент |
succ(x, n) |
возвращает n -й элемент, следующий за x |
pred(x) |
возвращает предшественника x |
pred(x, n) |
возвращает n -го предшественника x |
inc
, dec
, succ
и pred
могут выполниться с ошибкой, возбудив исключение EOutOfRange
или EOverflow
. (Если, конечно, код скомпилирован со включенными проверками на исключения.)type
Subrange = range[0..5]
Subrange
это диапазон int
, который может содержать значения от 0 до 5. Назначение любых других значений переменной типа Subrange
приведёт к ошибке компиляции или времени выполнения. Назначение базового типа одному из его диапазонов (и наоборот) разрешается.system
определяет важный тип Natural
как range[0..high(int)]
(high
возвращает максимально допустимое значение). Другие языки программирования принуждают использовать для работы с натуральными числами беззнаковые целые числа. Это часто бывает неправильно: вас не должны заставлять использовать беззнаковую арифметику лишь по той причине, что числа не могут быть отрицательными. Тип Natural
языка Nim позволяет избежать этой распространённой ошибки программирования.set
моделирует математическое понятие множества. Базовый тип множества может быть только перечислимым типом определённого размера, а именно: int8-int16
uint8/byte-uint16
char
enum
var s: set[int64] # Error: set is too large
{}
это пустое множество. Пустое множество совместимо по типу с любым конкретным типом множества. Конструктор также может быть использован для включения элементов (и диапазонов элементов):type
CharSet = set[char]
var
x: CharSet
x = {'a'..'z', '0'..'9'} # Здесь конструируется множество, содержащее буквы от
# 'a' до 'z' и цифры от '0' до '9'
Операция | Описание |
---|---|
A + B |
объединение двух множеств |
A * B |
пересечение двух множеств |
A - B |
разность двух множеств (A без элементов B ) |
A == B |
равенство множеств |
A <= B |
отношение подмножества (A является подмножеством B или эквивалентно B ) |
A < B |
строгое отношение подмножества (A является подмножеством B ) |
e in A |
принадлежность ко множеству (A содержит элемент e ) |
e notin A |
A не содержит элемент e |
contains(A, e) |
A содержит элемент e |
card(A) |
мощность A (количество элементов в A ) |
incl(A, elem) |
то же, что A = A + {elem} |
excl(A, elem) |
то же, что A = A - {elem} |
or
.[]
:type
IntArray = array[0..5, int] # массив целых чисел, индексированный от 0 до 5
var
x: IntArray
x = [1, 2, 3, 4, 5, 6]
for i in low(x)..high(x):
echo(x[i])
x[i]
используется для получения доступа к i
-му элементу x
. При доступе к элементам массива всегда производится проверка границ (либо во время компиляции, либо во время выполнения). Эту проверку можно отключить прагмами или вызовом компилятора с ключом --bound_checks:off
.len
возвращает длину массива. low(a)
возвращает наименьший возможный индекс массива a
, а high(a)
– наибольший возможный индекс.type
Direction = enum
north, east, south, west
BlinkLights = enum
off, on, slowBlink, mediumBlink, fastBlink
LevelSetting = array[north..west, BlinkLights]
var
level: LevelSetting
level[north] = on
level[south] = slowBlink
level[east] = fastBlink
echo repr(level) # --> [on, fastBlink, slowBlink, off]
echo low(level) # --> north
echo len(level) # --> 4
echo high(level) # --> west
level
определён как массив перечислений, индексированный другим перечислением, мы можем добавить следующие строки, чтобы дать возможность типу маяка разделяться по уровням, доступ к которым можно получить через их целочисленный индекс:type
LightTower = array[1..10, LevelSetting]
var
tower: LightTower
tower[1][north] = slowBlink
tower[1][east] = mediumBlink
echo len(tower) # --> 10
echo len(tower[1]) # --> 4
echo repr(tower) # --> [[slowBlink, mediumBlink, ...и т.д...
# Следующие строки не компилируются из-за несовпадения типов
#tower[north][east] = on
#tower[0][1] = on
len
вернёт длину только массива первого уровня. Чтобы ещё лучше показать вложенную природу LightTower
, можно было бы не писать предыдущее определение типа LevelSetting
, а вместо этого включить его непосредственно в тип первого измерения:type
LightTower = array[1..10, array[north..west, BlinkLights]]
type
IntArray = array[0..5, int] # массив, индексированный от 0 до 5
QuickArray = array[6, int] # массив, индексированный от 0 до 5
var
x: IntArray
y: QuickArray
x = [1, 2, 3, 4, 5, 6]
y = x
for i in low(x)..high(x):
echo(x[i], y[i])
int
, начинающимся с 0. Операции len
, low
и high
применимы для последовательностей. Нотация x[i]
может использоваться для доступа к i
-му элементу x
.[]
, соединённого с оператором преобразования массива в последовательность @
. Другой способ выделить память для последовательности состоит в вызове встроенной процедуры newSeq
.openarray
.var
x: seq[int] # ссылка на последовательность целых чисел
x = @[1, 2, 3, 4, 5, 6] # @ превращает массив в последовательность, размещённую в куче
nil
. Однако, большая часть операций над последовательностями не может работать с nil
(это приведёт к возбуждению исключения) по причинам, связанным с производительностью. Так что в качестве пустого значения желательно использовать пустую последовательность @[]
, а не nil
. Но @[]
создаёт объект последовательности в куче, поэтому надо будет искать решение, приемлемое для вашего конкретного случая.for
, использованная для последовательности, может работать с одной или двумя переменными. Если вы используете форму с одной переменной, то переменная будет содержать значение, предоставляемое последовательностью. Команда for
проходит по результатам, полученным из итератора items()
модуля system
. Но если вы используете форму с двумя переменными, тогда первая переменная содержит индекс позиции, а вторая – значение. В этом случае команда for
проходит по результатам итератора pairs()
из модуля system
. Примеры:for i in @[3, 4, 5]:
echo($i)
# --> 3
# --> 4
# --> 5
for i, value in @[3, 4, 5]:
echo("index: ", $i, ", value:", $value)
# --> index: 0, value:3
# --> index: 1, value:4
# --> index: 2, value:5
Примечание: Открытые массивы могут использоваться только в качестве параметров.Часто выясняется, что массивы с фиксированным размером недостаточно гибки: процедурам бывает нужно иметь дело с массивами разных размеров. Для этого есть тип открытого массива. Открытые массивы всегда индексируются целыми числами и нумерация начинается с 0. Для них доступны операции
len
, low
и high
. Любой массив с совместимым базовым типом может быть передан в качестве параметра открытого массива, тип индекса не имеет значения.var
fruits: seq[string] # ссылка на последовательность строк, которая
# инициализирована значением 'nil'
capitals: array[3, string] # массив строк с фиксированным размером
fruits = @[] # создаёт в куче пустую последовательность, на
# которую будет ссылаться 'fruits'
capitals = ["New York", "London", "Berlin"] # массив 'capitals' позволяет
# присвоить лишь три элемента
fruits.add("Banana") # последовательность 'fruits' динамически
# расширяется в ходе выполнения
fruits.add("Mango")
proc openArraySize(oa: openArray[string]): int =
oa.len
assert openArraySize(fruits) == 2 # процедура принимает последовательность
# в качестве параметра
assert openArraySize(capitals) == 3 # но и массив тоже
varargs
напоминает открытый массив. Однако, вдобавок он позволяет передать в процедуру любое число аргументов. Компилятор автоматически преобразует список аргументов в массив:proc myWriteln(f: File, a: varargs[string]) =
for s in items(a):
write(f, s)
write(f, "\n")
myWriteln(stdout, "abc", "def", "xyz")
# превращается компилятором в:
myWriteln(stdout, ["abc", "def", "xyz"])
varargs
последний в заголовке процедуры. Также можно выполнить преобразование типов в этом контексте:proc myWriteln(f: File, a: varargs[string, `$`]) =
for s in items(a):
write(f, s)
write(f, "\n")
myWriteln(stdout, 123, "abc", 4.0)
# превращается компилятором в:
myWriteln(stdout, [$123, $"abc", $4.0])
$
применяется к каждому аргументу, переданному через параметр a
. Заметьте, что $
, применённый к строкам, не делает ничего.Slice
, который содержит две границы, a
и b
. Сам по себе слайс не очень полезен, но другие типы коллекций определяют операторы, которые принимают объекты Slice
для задания диапазонов. var
a = "Nim is a progamming language"
b = "Slices are useless."
echo a[7..12] # --> 'a prog'
b[11..^2] = "useful"
echo b # --> 'Slices are useful.'
()
. Порядок полей в конструкторе должен совпадать с порядком полей в определении кортежа. Различные типы кортежей считаются эквивалентными, если они задают поля тех же типов с теми же именами в том же порядке.t.field
используется для доступа к полю кортежа. Другая нотация, t[i]
даёт доступ к i
-му полю (i
должно быть целочисленной константой).type
Person = tuple[name: string, age: int] # тип представляет персону, которая
# состоит из имени и возраста
var
person: Person
person = (name: "Peter", age: 30)
# то же, но менее читабельно:
person = ("Peter", 30)
echo(person.name) # "Peter"
echo(person.age) # 30
echo(person[0]) # "Peter"
echo(person[1]) # 30
# Вам не нужно объявлять кортежи в отдельной секции типов.
var building: tuple[street: string, number: int]
building = ("Rue del Percebe", 13)
echo(building.street)
# Следующая строка не скомпилируется, это разные кортежи!
#person = building
# --> Error: type mismatch: got (tuple[street: string, number: int])
# but expected 'Person'
# А эта работает, поскольку имена и типы полей те же.
var teacher: tuple[name: string, age: int] = ("Mark", 42)
person = teacher
splitFile
из модуля os
, которая возвращает одновременно каталог, имя и расширение пути. Для правильной распаковки кортежа вам надо использовать круглые скобки вокруг значений, в которые вы распаковываете кортеж, в противном случае вы назначите одно и то же значение каждой из этих переменных! Пример:import os
let
path = "usr/local/nimc.html"
(dir, name, ext) = splitFile(path)
baddir, badname, badext = splitFile(path)
echo dir # выводит usr/local
echo name # выводит nimc
echo ext # выводит .html
# А следующее выведет одну и ту же строку:
# `(dir: usr/local, name: nimc, ext: .html)`
echo baddir
echo badname
echo badext
var
или let
. Следующий код не будет компилироваться:import os
var
path = "usr/local/nimc.html"
dir, name, ext = ""
(dir, name, ext) = splitFile(path)
# --> Error: '(dir, name, ext)' cannot be assigned to
ref
, неотслеживаемые – ключевым словом ptr
.[]
может использоваться для разыменования ссылки, то есть для получения элемента, на который указывает ссылка. Операторы .
(доступ к полю кортежа/объекта) и []
(оператор индексирования массива/строки/последовательности) выполняют неявное разыменование для ссылочных типов:type
Node = ref NodeObj
NodeObj = object
le, ri: Node
data: int
var
n: Node
new(n)
n.data = 9
# не надо писать n[].data, это сильно сбивает с толку!
new
. Для работы с неотслеживаемой памятью могут использоваться процедуры alloc
, dealloc
и realloc
. Документация модуля system
содержит дополнительную информацию по этим вопросам.nil
.nil
. Nim использует процедурный тип для реализации техник функционального программирования.proc echoItem(x: int) = echo(x)
proc forEach(action: proc (x: int)) =
const
data = [2, 3, 5, 7, 11]
for d in items(data):
action(d)
forEach(echoItem)
import
. Экспортироваться могут только символы верхнего уровня, отмеченные звёздочкой (*
):# Модуль A
var
x*, y: int
proc `*` *(a, b: seq[int]): seq[int] =
# создать новую последовательность:
newSeq(result, len(a))
# перемножить две целочисленных последовательности:
for i in 0..len(a)-1: result[i] = a[i] * b[i]
when isMainModule:
# проверить новый оператор ``*`` для последовательностей:
assert(@[1, 2, 3] * @[1, 2, 3] == @[1, 4, 9])
A
экспортирует x
и *
, но не y
.isMainModule
, которая истинна, если модуль скомпилирован в качестве основного файла. Это очень полезно для внедрения внутрь модуля тестов, как показано в предыдущем примере.import
.# Модуль A
type
T1* = int # Модуль A экспортирует тип ``T1``
import B # компилятор начинает разбирать B
proc main() =
var i = p(3) # работает, поскольку B здесь уже полностью разобран
main()
# Модуль B
import A # A пока ещё не разобран до конца! Будут импортированы только те
# символы, которые разобраны в A на данный момент.
proc p*(x: A.T1): A.T1 =
# это работает, поскольку компилятор уже добавил T1 к таблице символов A
result = x + 1
module.symbol
. Если символ неоднозначен, он должен квалифицироваться. Символ является неоднозначным, если он определён в двух (или более) разных модулях и оба модуля импортируются третьим:# Модуль A
var x*: string
# Модуль B
var x*: int
# Модуль C
import A, B
write(stdout, x) # ошибка: x неоднозначен
write(stdout, A.x) # ошибки нет: используется квалификатор
var x = 4
write(stdout, x) # неоднозначности нет: используется x модуля C
# Модуль A
proc x*(a: int): string = $a
# Модуль B
proc x*(a: string): string = $a
# Модуль C
import A, B
write(stdout, x(3)) # ошибки нет: вызывается A.x
write(stdout, x("")) # ошибки нет: вызывается B.x
proc x*(a: int): string = nil
write(stdout, x(3)) # неоднозначность: какой `x` вызывать?
import
забирает все экспортируемые символы. Это можно изменить, указав исключаемые символы с квалификатором except
.import mymodule except y
import
, импортирующую все экспортированные символы. Можно импортировать только перечисленные символы с помощью команды from import
:from mymodule import x, y, z
from
может также принудительно задавать пространства имён для символов, в результате чего символы будут доступны, но для их использования потребуется указать квалификатор.from mymodule import x, y, z
x() # использовать x без квалификации
from mymodule import nil
mymodule.x() # нужно квалифицировать x, указав имя модуля в качестве префикса
x() # использование x без квалификации приведёт к ошибке компиляции
from mymodule as m import nil
m.x() # m это псевдоним для mymodule
include
делает нечто, фундаментально отличающееся от импортирования модуля: она вставляет на своё место содержимое файла. Эта команда весьма полезна для разбиения большого модуля на несколько файлов:include fileA, fileB, fileC
К сожалению, не доступен сервер mySQL