14

In Perl 5, I can create a filehandle to a string and read or write from the string as if it were a file. This is great for working with tests or templates.

For example:

use v5.10; use strict; use warnings;

my $text = "A\nB\nC\n";

open(my $fh, '<', \$text);

while(my $line = readline($fh)){
    print $line;
}

How can I do that in Perl 6? The following doesn't work for Perl 6 (at least not for my instance of Perl6 running on MoarVM 2015.01 from the January 2015 release of Rakudo Star on 64-bit CentOS 6.5):

# Warning: This code does not work
use v6;

my $text = "A\nB\nC\n";

my $fh = $text;

while (my $line = $fh.get ) {
    $line.say;
}
# Warning: Example of nonfunctional code

I get the error message:

No such method 'get' for invocant of type 'Str'
   in block <unit> at string_fh.p6:8

It's not very surprising that Perl5's open(my $fh, '<', \$text) is not the same as Perl6's my $fh = $text;. So the question is: How does one create a virtual file handle from a string in Perl 6 like open(my $fh, '<', \$str) in Perl 5? Or is that something that has yet to be implemented?

UPDATE (writing to a filehandle in Perl 5)

Likewise, you can write to string filehandles in Perl 5:

use v5.10; use strict; use warnings;

 my $text = "";
 open(my $fh, '>', \$text);

 print $fh "A";
 print $fh "B";
 print $fh "C";

 print "My string is '$text'\n";

Outputs:

 My string is 'ABC'

I haven't seen anything remotely similar in Perl 6, yet.

Pat
  • 34,832
  • 18
  • 68
  • 86
Christopher Bottoms
  • 10,220
  • 7
  • 44
  • 87
  • 1
    Btw. the `while (my $line ...) { ... }` code doesn't lexically scope `$line` the way you think it does. The `$line` ends up lexically scoped to the surrounding context, the main body of the script. It is not scoped to inside the following braces. Instead, lexical variable declarations using `my` are scoped to their enclosing braces. Parameters in contrast *are* scoped to the following braces because those are the code block that acts on the parameters. The `$line` in the `-> $line` code in the first (only) answer below is a parameter. – raiph Feb 24 '15 at 22:52
  • In P5, `$line` is an undeclared variable outside the `while` statement (`use strict; use warnings;` will reveal this) whereas in P6 it would be known. – raiph Feb 25 '15 at 00:49
  • @raiph Oh, okay. Now I see that your first comment is entirely referring to Perl 6. I just tried a `while (my $line = ...` loop in Perl 6 and you are right that `$line` is scoped outside the loop. However, it doesn't have a value outside the loop. After the loop, `$line.say` prints `(Any)`, meaning that it is in scope, but is basically undefined. – Christopher Bottoms Feb 25 '15 at 17:27
  • If `$line.say` prints `(Any)` then that's the last "value" assigned to `$line` either in the `while` statement's conditional expression `my $line = ...` (note that the parens around the conditional are unnecessary) or in the subsequent `while` statement's block. – raiph Sep 03 '15 at 23:05

1 Answers1

11

Reading

The idiomatic way to read line-by-line is the .lines method, which is available on both Str and IO::Handle.

It returns a lazy list which you can pass on to for, as in

my $text = "A\nB\nC\n";

for $text.lines -> $line {
     # do something with $line
}

Writing

my $scalar;
my $fh = IO::Handle.new but
         role {
             method print (*@stuff) { $scalar ~= @stuff };
             method print-nl        { $scalar ~= "\n" }
         };

$fh.say("OH HAI");
$fh.say("bai bai");

say $scalar
# OH HAI
# bai bai

(Adapted from #perl6, thanks to Carl Mäsak.)

More advanced cases

If you need a more sophisticated mechanism to fake file handles, there's IO::Capture::Simple and IO::String in the ecosystem.

For example:

use IO::Capture::Simple;
my $result;
capture_stdout_on($result);
say "Howdy there!";
say "Hai!";
capture_stdout_off();
say "Captured string:\n" ~$result;
raiph
  • 26,254
  • 3
  • 45
  • 89
Christoph
  • 149,808
  • 36
  • 172
  • 230
  • I like the pedagogical value of using the explicit `-> $foo` bit, but in practice the `for ...` statement will often be the more succinct `for $text.lines { .foo }` where the `.foo` method is called on "it". ("It" is the variable `$_` which is automatically assigned anyway when one writes "topicalizers" like `for`.) – raiph Feb 24 '15 at 22:41
  • 1
    @raiph It's a matter of preference. I used to use `$_` in Perl 5 a lot, but now prefer more explicit variables. It may be slightly more verbose, but I'm going for maintainability over succinctness. I'm glad Perl 6 is also TIMTOWTDI. – Christopher Bottoms Feb 25 '15 at 15:34