I'm trying to write an Excel notebook in Perl6, using Excel::Writer::XLSX.
I'm using Inline::Perl5 via use Excel::Writer::XLSX:from<Perl5>
Specifically, I want to write a row like I did in Perl5:
$worksheet -> write_row(0,0, \@line);
but this gives error
To pass an array, hash or sub to a function in Perl 6, just pass it as is. For other uses of Perl 5's ref operator consider binding with ::= instead. Parenthesize as (...) if you intended a capture of a single variable.
so I try advice1:
$worksheet.write_row(0,0, @line)
which gives error
Not an array ref in call to write_row() at -e line 0.
advice2:
$worksheet.write_row(0,0, ::=@line);
Not an array ref in call to write_row() at -e line 0.
$worksheet.write_row(0,0, (@line));
which gives the same errors.
How can I write an array to a row using Excel::Writer::XLSX in Perl6?
The solution is to write $@array
instead of \@array
:
$worksheet.write_row(0,0, $@line)
The rest of this answer is a brief guide on writing code using installed foreign language adaptors and dealing with any problems that arise. I intend it to be a general resource, an answer that goes well beyond this specific question.
It starts with the obvious question "Why $@foo
?", discusses how to successfully use foreign language adaptors, and closes by explaining why the warning message was unhelpful.
$@foo
?Writing $@foo
to pass @foo
as an array reference to P5 is a simple and natural use of an existing P6 feature.
That said, users don't need to understand this $
feature of P6, nor do they need to know how Inline::Perl5
uses it, to get done what they want done (to write P6 code that passes an array reference to a function from a P5 module).
So the main answer to "why $@foo
?" is that you write it that way and it works. \o/
I got the solution for passing an array reference from Inline::Perl5's README:
HASH
andARRAY
references are made automatically if the Perl 6 objects are containerized:
$p5obj.takes-an-array: [<a b c>];
$p5obj.takes-an-array-ref: $[<a b c>];
(The word "containerized" refers to a P6 concept that lizmat explains well for those who know P5 in her Containers in P6 article. But containership is not actually technically relevant to why $@foo
means an array ref in P5 when using Inline::Perl5
. Yes, it works. No, it didn't have to be that way.)
$@foo
in P6 mean an array ref in P5?The reason that writing $@foo
is the correct thing to do isn't because the P6 language or compiler says it is.
It is an appropriate P6 feature, but the reason it's the correct thing to do is that niner (the author of Inline::Perl5
) says so.
$@foo
?Presumably because it:
Is easy to write for newbies;
Will make sense for this purpose as someone gets to know P6;
Is easy for niner to document;
Is easy to convert from its P6 meaning (an Array
in a Scalar
) to the target P5 meaning (a P5 array reference) in a performant manner.
Inline::Perl5
is one of several foreign language adaptors. These adaptors allow P6 code to embed code written in those foreign languages and/or to use modules written in those foreign languages.
When the Rakudo P6 compiler sees the :from<Perl5>
in a use
statement, it implicitly invokes a previously installed P6 module called Inline::Perl5
.
Inline::Perl5
installs marshaling code that automatically maps P6 code to and from P5 code so P6 and P5 can work together with minimal fuss.
Ideally adaptors would just do their job and you would never have to think about them. But:
While the obvious thing to map 42
in P6 to in any and all foreign languages is their value representing the integer 42
, the mapping isn't always so straight-forward for higher level data structures, functions, references, etc. Sometimes it's amazing what can be done writing P6 code (you can create a P6 class that's a sub-class of a P5 class writing it exactly as you would if it were regular P6 code!) but sometimes you have to follow a rule (as in this case of how to pass a P6 array to P5 as an array reference). See the Adaptor documentation section below.
The P6 language and compiler aren't aware that mapping is going on. So any error or warning messages they display can be unhelpful, even misleading. Also, adaptor bugs are separate from bugs in P6, in its compilers, and in the foreign language modules being used. See the Warnings and errors section below.
Making the automatic mapping that foreign language adaptors make happen is far from automatic. Talented devs have to write adaptor code that does all the marshaling of data, exceptions, etc.
Given enough elapsed time (years) an adaptor may be able to close in on the ideal in which you never have to think about the adaptor's existence if you just want to use that language's modules or code in P6. You just use it and it always works just like it does in the foreign language.
One way to close in on that ideal more quickly for you, dear reader, and for all of us, is to use foreign modules via the existing adaptors and write SO questions and file issues when particular features don't seem to work. Thank you @con, niner, and everyone else who makes this happen.
The only way to know for sure what P6 code you're supposed to write to make use of a foreign language module that you've used via :from<...>
is to:
Read the foreign language module's documentation to see what it expects; and then
Read the adaptor's documentation to see how to write the corresponding P6 code that gives the foreign language and the foreign language module what they expect.
In this instance, in which we're talking about use of :from<Perl5>
, the adaptor is Inline::Perl5
. Its documentation is currently its github project repo README. So to see what you're supposed to write to give P5 and the P5 module what they expect, read that document.
Each adaptor will have its own documentation; see its listing on modules.perl6.org for a link.
If anything goes wrong when using a foreign language module in P6 (that doesn't go wrong when using the same code directly in that foreign language) then:
Make sure you've read the relevant adaptor's doc;
If you get a warning or error message, make sure to refer to the adaptor's documentation and its issues queue to see if it sheds light on that particular message;
If you think there's a bug in how things are (or aren't) working in P6 when using a foreign language module that works fine when used directly in that foreign language, refer to the adaptor's issue queue. For example, if you're using :from<Perl5>
then refer to Inline::Perl5
's issue queue. If you decide to post something, either post here if you're not sure it's a bug or in the adaptor's issue queue if you are.
Because the P6 language and compiler aren't aware that mapping is going on, warning and error messages can be misleading in the context of learning to use any aspects of a foreign language adaptor that involve following rules such as the $@foo
rule of this SO.
In the case of P5 issues this can be exacerbated by P6 trying to be helpful to P5 coders writing P6 code, and that attempt backfiring when using Inline::Perl5
. The example in the question is a good example:
To pass an array, hash or sub to a function in Perl 6, just pass it as is.
P6 thinks you might be used to writing array references using a prefix \
. It's thinking you might be unaware that you don't need to write the slash in P6. It doesn't realize you're trying to insist on getting an array reference that P5 will understand via Inline::Perl5
.
For other uses of Perl 5's ref operator consider binding with ::= instead.
P6 thinks you're trying to do what in P5 is typically done with \
, not because you want to work with P5 but because you know P5, want to get something done in P6 without involving P5, and are hoping the exact same syntax works.
(Btw, the ::=
advice definitely couldn't help -- because ::=
is not yet implemented!)
Parenthesize as (...) if you intended a capture of a single variable.
P6 isn't thinking you're trying to make a P5 function work with an array reference. It thinks you're trying to create a P6 Capture
.
Its advice is supposed to be interpreted as suggesting you write \(@foo)
instead of \@foo
to create a Capture
containing @foo
.
It has warned about use of \@foo
to mean a Capture
precisely because P5 devs might write it thinking it would create an array reference.
In summary, P6 doesn't know that Inline::Perl5
exists, or that it is going to do anything with your code. Instead, its advice is an attempt to translate P5 syntax and concepts that it thinks you're using, to corresponding syntax and concepts in P6. It's not an attempt to advise how to write P6 code suitable for mapping to P5 via Inline::Perl5
.
That all said, I imagine it's possible that adaptor devs and core P6 devs will one day modify P5 related warning and error messages in the light of usability concerns such as have been raised by this SO.
::=
is an infix operator, so you would need something on the left side of it. If you used it you would find out that it also isn't implemented yet. That doesn't matter though, because it still wouldn't help.
The compiler is complaining because you are writing code that looks like Perl5 not Perl6. It doesn't know what you are really trying to accomplish.
In Perl5 \
creates a reference. In Perl6 there isn't really anything like that.
use v5.12;
my @a;
my $a = \@a;
say ref $a; # ARRAY
say ref \$a; # REF
In Perl6 \
is used for creating a Capture.
use v6.d;
my @a;
my $a = \(@a);
say $a.^name; # Capture
Since Perl5 doesn't have a Capture and Perl6 doesn't have a REF
, the interface layer translates the Capture into a REF
for use with Perl5.
An array in Perl6 is already a sort of reference, so that is why \(@a)
in Perl6 is like \\@a
in Perl5.
As a way of testing, I'm going to use Scalar::Util::reftype.
use v6.d;
use Scalar::Util:from<Perl5> <reftype>;
my @a;
say reftype @a; # ARRAY
say reftype \(@a); # REF
That first one only works because reftype
has a prototype that takes a single argument. Most subroutines in Perl5 don't have a prototype. Even when they do, method calls ignore prototypes.
In Perl5, arrays generally flatten into outer lists.
use v5.12;
my @a = 3,2,1;
my @b = 5,4,@a;
say for @b;
# 5
# 4
# 3
# 2
# 1
To make calling into Perl5 code more like Perl5, arrays get flattened.
use v6.d;
use Inline::Perl5;
my $p5 = Inline::Perl5.new;
$p5.run( 'use v5.12; sub fubar { say for @_ }' );
$p5.call( 'fubar', 1,2,3,[4,5] );
# 1
# 2
# 3
# 4
# 5
One way to prevent flattening in Perl6 is to make it an item.
use v6.d;
my @a = 1,2,3;
.say for @a;
# 1
# 2
# 3
.say for $@a;
# [1,2,3]
So let's try that when calling into Perl5.
use v6.d;
use Inline::Perl5;
my $p5 = Inline::Perl5.new;
$p5.run( 'use v5.12; sub fubar { say for @_ }' );
$p5.call( 'fubar', 1,2,3,$[4,5] );
# 1
# 2
# 3
# ARRAY(0x55cedda35300)
$p5.call( 'fubar', 1,2,3,[4,5].item );
# 1
# 2
# 3
# ARRAY(0x55ceddbcfc38)
$p5.call( 'fubar', 1,2,3,\[4,5] );
# 1
# 2
# 3
# REF(0x55ceddbcfb90)
So $(…)
and .item
both work for preventing arrays from being flattened when calling into Perl5 code without it getting double referenced like \(@)
does.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With