#return #raku #subroutine
Вопрос:
…
#!/usr/bin/env raku
# -*-perl6-*-
# 2021-5-30: Example of how a sub does not seem to be able to return 2 Hashes...
sub GetHashes
{
my %H = 100 => 2149, 101 => 2305, 102 => 2076, 103 => 1767, 104 => 1743 ;
my %G = 100 => 21493, 101 => 23053, 102 => 20763, 103 => 17673, 104 => 17433 ;
return( %H, %G );
}
my ( %H, %G ) = GetHashes ;
%H.say;
%G.say;
…
Я старый программист FORTRAN/perl5, который решил попробовать выучить раку. Приведенный выше код не работает. Что я упускаю? В perl5 я бы вернул ссылки на хэши и снял ссылку с вызывающего абонента. Что здесь?
Ответ №1:
Что я упускаю?
Два ответа среди многих возможных ответов таковы:
- У тебя отсутствует толстая кишка.
В частности, вы можете изменить:
my ( %H, %G ) = GetHashes ;
Для:
my ( %H, %G ) := GetHashes ; ^
- Вы пропускаете ответ @p6steve.
В частности, вы можете изменить:
my ( %H, %G ) = GetHashes ;
Для:
my ( $H, $G ) = GetHashes ;
Резюме состоит в том, что Raku поддерживает несколько способов сделать некоторые места хранения либо «содержащими» некоторые данные, либо «являющимися ссылкой» на некоторые данные. Вы использовали «назначение списка», но вам нужно использовать одну из альтернатив, например, наши ответы.
Назначение ( =
)
Назначение означает копирование данных в некоторые места хранения, которые в конечном итоге «содержат» скопированные данные.[1]
Если вы используете назначение списка, то чем больше элементов в списке, тем больше байтов копируется. Если у вас миллион элементов, маловероятно, что вы захотите назначить их с помощью назначения списка. И могут быть и другие причины, по которым вы не хотите использовать назначение, как вы обнаружили.
Рассмотрим, например, my @array1 = 1, 2
. Этот код:
- Создает a
List
из двух элементов —(1, 2)
— в качестве данных, которые должны быть назначены/скопированы; - Создает новый пустой
Array
массив и «привязывает» этот новый массив к «символу» (имя переменной)@array1
; - Повторяет
List
элементы и, для каждого:3.1 Добавляет новый соответствующий пустой «контейнер» (a
Scalar
) в целевой массив, готовый к приему некоторых скалярных данных;3.2 Копирует скалярное (одиночное) значение из
List
в новыйScalar
контейнер в массиве.
Обратите внимание, как это приводит к созданию трех хранилищ во время выполнения: массива и двух отдельных Scalar
s в качестве первых двух индексируемых элементов этого массива. Все три из этих хранилищ обновляются, при этом массив Scalar
добавляется два s, а два Scalar
s получают два значения, скопированные из списка.
Привязка ( :=
)
Привязка означает копирование ссылок (указателей) на данные. Сами данные не копируются. Если есть, скажем, две ссылки на данные, то копируется очень мало, независимо от того, насколько велики данные. Один из способов придерживаться копирования ссылок, а не данных, на которые ссылаются, — это использовать привязку.
Рассмотрим, например my @array2 := 1, 2
. Этот код:
- Конструирует a
List
из двух элементов(1, 2)
; - Привязывает ссылку
List
на «символ» (имя переменной)@array2
.
Обратите внимание, что, в отличие от приведенного выше примера присвоения, в этом примере привязки слева от обновляемого места хранения () оказывается только одно место хранения ( @array2
) :=
, соответствующее тому, что справа обрабатывается как одно значение (a List
).
Слева от операции привязки может быть несколько мест хранения. Рассмотрим ваш пример, my ( %H, %G ) := GetHashes;
. Этот код:
- Оценивает
GetHashes
, возвращая одинList
из двух хэшей; - Повторяет список значений/ссылок справа от
:=
, привязывая каждое по очереди к списку целевых мест хранения слева:=
.
Обратите внимание, как, в отличие от предыдущего примера привязки, в этом случае обновляются два места хранения (связанные с символами %H
и %G
), соответствующие двум хэшам в List
возвращаемом GetHashes
вызове.
Влияние назначения списка в исходном коде
С вашим кодом, как это было , вы копировали все данные/значения с правой стороны =
, пункт за пунктом (таким образом, в общей сложности 10 действий по копированию), в хэш, привязанный к первой переменной ( %H
) слева.
Это связано с точной семантикой назначения (использования =
), которая определяется отдельными целями слева =
, начиная с первой, пока она не будет заполнена, затем второй и так далее.
Если целью назначения является Associative
(например %H
), он жадно глотает все, что может, справа от =
. Это называется «назначение списка».
Итак , что происходит с вашим исходным кодом, %H
так это то, что вы поглощаете первые пять элементов в первом хэше, возвращенном GetHashes
, а затем продолжаете, поэтому пять элементов из второго хэша, возвращенного подменой, также назначаются %H
.
И поскольку ключи этой второй партии пар совпадают с ключами первой, эта вторая партия перезаписывает первые пять.
Таким %H
образом, в итоге получается только вторая партия пар, и %G
она остается пустой, результат, который вы видите.
Аналогичная сделка применяется для Positional
целей назначения, например, для переменной с именем @array
.
Единственными целями назначения в стандартном Raku, которые не являются жадными таким образом, являются Scalar
такие цели, как $bar
. Таким образом, вы можете с пользой написать, скажем , my ($bar, @bam) = 1, 2, 3;
и в конечном итоге получить $bar
назначение 1
, а @bam
в конечном итоге-как [2 3]
. Отсюда и ответ @p6steve.
Сноски
[1] Если =
используется в некотором коде, но присвоение контейнерам слева-это не то, что означает это выражение, вместо этого Раку делает то, что предположительно имеет в виду автор. Например:
constant list = 1, 2, 3;
не временно присваиваетList
(1, 2, 3)
символ символуlist
во время выполнения, а вместо этого постоянно связывает его во время компиляции. Другими словами, он делает то жеconstant list := 1, 2, 3;
самое, что и . (Большинство людей просто пишутconstant list = 1, 2, 3;
.)my int @list = 1, 2, 3;
не добавляет никакихScalar
контейнеров в собственный массив@list
, а вместо этого напрямую записывает собственные целые числа.
Ответ №2:
Ну, я виноват в том, что увлекаюсь назначениями, и мне нравятся многие ссылочные стили perl5, так как я вырос с ними. Как и следовало ожидать, у Раку есть несколько других способов справиться с этой ситуацией:
my ( $H, $G ) = GetHashes ;
$H.say;
$G.say;
В этом случае Раку достаточно умен, чтобы автоматически <a rel=»noreferrer noopener nofollow» href=»https://docs.raku.org/routine/» rel=»nofollow noreferrer»>деконтировать скаляры и указать базовый хэш. Эта прозрачность (т. е. автоматическое разыменование) является одним из улучшений Raku по сравнению с perl5 и заметным изменением в том, как работают сигилы. Требуется немного привыкнуть — но очень мощный и лаконичный ИМО.
В адвент-постах Raku (Perl6) содержится много полезной учебной информации-смотрите этот обзор для отличного обзора.