#nim-lang
#nim-lang
Вопрос:
Я написал процедуру Nim, взяв в качестве var
параметров два объекта. Каждый из них представляет собой объект с int
полем level
. Прежде чем приступить к реальной работе процедуры, я хочу упорядочить параметры по порядку, по которому они больше level
, поэтому я делаю это:
proc subroutine(param1: var MyObject, param2: var MyObject) =
var large: ptr MyObject = param1.addr
var small: ptr MyObject = param2.addr
# If in wrong order by level, swap large and small
if large.level < small.level:
large = param2.addr
small = param1.addr
# The rest of the proc references, only variables large and small, e.g.,
large.level = 1
small.level = 2
Кажется, это работает для моего приложения, но я замечаю, что в документации Nim этот ptr
тип называется «небезопасным», и его предлагается использовать только для низкоуровневых операций. Существует «безопасный» ссылочный тип ref
, и его рекомендуется использовать ref
, если вы действительно не хотите выполнять ручное управление памятью.
Я не хочу выполнять ручное управление памятью, и я хочу, чтобы сборщик мусора Nim обрабатывал память для этих параметров для меня, но я не вижу способа обеспечить безопасность ref
для двух параметров.
Я действительно хочу иметь возможность написать алгоритм (который значительно сложнее, чем простой код, который я показал) в терминах переменных large
and small
, а не param1
and param2
. В противном случае, если я могу ссылаться только на параметры param1
и param2
, не создавая для них эти псевдонимы, мне пришлось бы копировать и вставлять один и тот же алгоритм дважды, чтобы отдельно обрабатывать случаи param1.level < param2.level
и param1.level >= param2.level
.
Есть ли более идиоматический способ Nim сделать что-то подобное, без использования ptr
типа?
Ответ №1:
Вы не можете превратить безопасный объект в небезопасный и наоборот, кроме как выполнив копирование объекта.
Обычные переменные хранятся в стеке и, следовательно, будут уничтожены, когда их область действия функции существует, но ссылка может быть сохранена в глобальной переменной и доступна позже. Если бы это было возможно, чтобы сделать его безопасным, компилятору / языку нужно было бы знать какой-то способ извлечения переменной из стека, чтобы она оставалась действительной после существования ее области видимости, или выполнить копирование вручную за вашей спиной, или какую-то другую волшебную вещь.
Это также причина, по которой небезопасно использовать адрес переменной. Вы не можете гарантировать его срок службы, потому что вы можете сохранить этот адрес где-нибудь еще и попытаться использовать его позже. Однако, с точки зрения гарантий памяти, эти переменные должны быть активными, по крайней мере, на время вашего вызова proc, поэтому должно быть безопасно использовать эти псевдонимы адресов в этом процессе, не беспокоясь.
Тем не менее, вы можете переписать свой код, чтобы использовать промежуточный прокси-процесс, который выполняет проверку и, таким образом, передает правильные переменные в каждом слоте. Промежуточный процесс гарантирует, что одна из переменных всегда будет большой, и вы можете избежать использования небезопасных ссылок:
type
MyObject = object
level: int
proc subroutine_internal(large: var MyObject, small: var MyObject) =
assert large.level >= small.level
large.level = 1
small.level = 2
proc subroutine(param1: var MyObject, param2: var MyObject) =
if param1.level < param2.level:
subroutine_internal(param2, param1)
else:
subroutine_internal(param1, param2)
proc main() =
var
a = MyObject(level: 3)
b = MyObject(level: 40)
subroutine(a, b)
echo a, b
main()
Комментарии:
1. Спасибо! Не могли бы вы сказать, что более идиоматичным Nim (или более безопасным / рекомендуемым) является использование вашего решения вместо использования
ptr
? Или, возможно, более идиоматично не использоватьvar
параметры так свободно, а вместо этого использовать более функциональный подход, возвращая новые объекты, которые представляют измененные версии параметров?2. @DaveDoty на самом деле не знал бы, поскольку я не следую никаким тенденциям Nim и не смотрю на то, что делают другие. Я бы предпочел версию, отличную от ptr, с точки зрения безопасности, вы можете вернуться через несколько месяцев, посмотреть на эти указатели и сделать то, что вы не должны были делать при первом написании кода. Nim может быть довольно низкоуровневым и обязательным, поэтому я бы не стал принуждать материал к функциональному стилю, если вы действительно этого не предпочитаете или это дает вам какое-то преимущество.
Ответ №2:
type
MyObject = object
level: int
proc subroutine(param1, param2: var MyObject) =
if param1.level > param2.level:
swap(param1, param2)
echo param1.level
echo param2.level
var
p1 = MyObject(level: 7)
p2 = MyObject(level: 3)
subroutine(p1, p2)
p2.level = 13
subroutine(p1, p2)
Для этого случая в Nim есть специальная процедура swap(). Параметры Var передаются внутри как указатели, поэтому при передаче параметров копирование не требуется, и swap должен выполнять копирование как можно более оптимально.
Конечно, могут быть случаи, когда использование объектов ref вместо объектов value может иметь преимущества. Ссылки Nim являются управляемыми указателями. Вы можете найти больше информации в руководствах, и мой находится по адресу http://ssalewski.de/nimprogramming.html#_value_objects_and_references .
Комментарии:
1. Это очень круто, я продолжаю учиться у тебя, @Salewski, но этот код изменяет p1 и p2, поэтому второй результат равен (3,13), когда ожидается, что он будет (7,13)
2. Ну, код делает то, что я ожидал, и то, что, как я думал, было предназначено — какая-то сортировка параметров по полю уровня. Вопрос в том, действительно ли мы можем доверять swap() только для внутренней замены указателей, но не для дорогостоящей копии содержимого данных. Если производительность действительно критична, и мы не доверяем swap(), тогда нам придется протестировать или посмотреть сгенерированную сборку (swap — это процедура компилятора intern magic). Но метод мистера Ханкевича тоже хорош.
3. В конце мне больше не нравится мой собственный ответ, поскольку я не до конца понимаю, что на самом деле задумано. И поскольку оба параметра являются параметрами var, swap() действительно скопирует содержимое, так что, возможно, это не то, что было запрошено, извините. Но почему бы просто не вызвать «if a.level < b.level: p(a, b) else: p (b, a)»? Это было бы похоже на ответ Ханкевича.