Дженерики и конвертеры в Nim

в 10:10, , рубрики: converters, generics, metaprogramming, nim, дженерики, метапрограммирование, Программирование, метки:
Логотип языка Nim

Всем привет! В данной статье я постараюсь рассказать, что такое generic процедуры и converter’ы в Nim (и показать примеры их использования)

Что такое Nim? Nim – компилируемый (в C, C++, Objective C и JS) высокоуровневый язык программирования со сборщиком мусора, имеющий три основных цели (в порядке приоритета): производительность, выразительность, элегантность. Официальный сайт языка, репозиторий на GitHub.
Также в Nim достаточно развито метапрограммирование (дженерики, шаблоны, макросы).

Для начала я покажу, как выглядит процедура, которая имеет один аргумент типа int, и возвращает тоже int:

proc plusOne(arg: int): int = 
  return arg + 1
echo plusOne(5)  # Выведет 6

Как я думаю, тут всё предельно ясно (прибавляем число 1 к аргументу arg типа int и возвращаем результат)

Дженерики в Nim — это процедуры, которые могут принимать несколько типов (компилятор сделает отдельную версию процедуры для каждого типа, который использовался с данной процедурой).

1 пример — обычный дженерик

Этот пример выводит длину объекта, если для данного объекта имеется процедура len (почти что полный аналог __len__ в Python):

proc tryLen[T](something: T) = 
  when compiles(something.len):  # something.len это тоже самое, что len(something)
    echo something.len
  else:
    echo "У этого типа не объявлена процедура `len`"

# Объявим тип нового объекта, у которого не будет процедуры `len` (так как мы её не объявляли)
type MyObject = object
# Создадим сам объект
let myObj = MyObject()
tryLen([1, 2, 3])  # Выведет 3
tryLen("Hello world!")  # Выведет 12
tryLen(myObj)  # Выведет "У этого типа не объявлена процедура `len`"

Этот пример намного сложнее предыдущего, я постараюсь кратко объяснить, что тут происходит:

Для начала мы объявляем саму процедуру tryLen, которая принимает аргумент something типа T (T — это чаще всего используемое название для неопределённого типа, вместо T можно написать abcd, и всё будет работать точно так же).
Затем мы используем специальное условие when compiles (это аналог if, но условие должно быть известно во время компиляции, и сам when в скомпилированный код не попадает). Если это условие выполняется для данного типа — выводим результат процедуры len для данного аргумента, если не выполняется — выводим сообщение.
Затем мы создаём объект типа MyObject, и применяем tryLen к массиву [1, 2, 3], строке «Hello world!», и нашему объекту.

2 пример — конвертер

Он служит для неявного конвертирования одного типа в другой (но он не особо приветствуется самими разработчиками языка, так как в этом случае всё-таки «явное лучше неявного»).
В данном примере мы сделаем конвертер, который конвертирует наш тип объекта MyObject в число (в данном случае — просто 1):

type MyObject = object
let myObj = MyObject()

# Если процедура или конвертер маленькие, то можно сразу написать возвращаемое значение без return
# Имя конвертера не играет какой-либо особенной роли
converter toInt(b: MyObject): int = 1
# Конвертер можно вызвать явно (однако в этом случае лучше сделать обычную процедуру)
echo myObj.toInt + 1  # Выведет 2
# Или он сам может вызываться неявно:
echo myObj + 1  # Тоже выведет 2, так как toInt неявно конвертировал myObj в число 1

Использование конвертеров — спорная тема (в стандартной библиотеке языка они почти не используются), но я отношусь к ним нейтрально.

Последний пример — дженерик конвертер

Да, звучит страшно, но на самом деле всё не так сложно. С помощью него мы сможем сделать Nim чуть более похожим на Python (а именно — неявно преобразовывать некоторые типы к bool)

converter toBool[T](arg: T): bool = 
  # Если для данного типа аргумента имеется процедура len
  when compiles(arg.len):
    arg.len > 0
  # Если аргумент можно сложить с числом того же типа T
  # T(0) используется для того, чтобы условие одновременно совпадало
  # со всеми числовыми типами (т.е мы конвертируем 0 в данный тип)
  elif compiles(arg + T(0)):
    arg > 0


if [1, 2, 3]:  # Длина массива
  echo "True!"
if @[1, 2, 3]:  # Длина последовательности
  echo "True too!"
if "":  # Пустая строка
  echo "No :("
if 5:  # Integer
  echo "Nice number!"
if 0.0001:  # Float
  echo "Floats are nice too!"

Спасибо за чтение! Я надеюсь, что вы смогли почерпнуть для себя что-то новое.

Источники

Мануал
Википедия
А также пользователи Nim, которые мне подсказывали решение различных проблем.

Автор: Tiberiumk

Источник

Поделиться

* - обязательные к заполнению поля