Saturday, February 27, 2010

E03: Laziness: The Catch

So, jnthn++ fixed the Nominal type check failed for parameter '@a'; expected Positional but got GatherIterator instead issue. Errr, sort of. Here's my sum sub:


sub sum(@a) {
+@a ?? @a.reduce({ $^a + $^b }) !! 0;
}


Now it does get called properly if you say sum(@costs.grep({$_ >= 1000})). But even though it appears to be a very simple function, it doesn't work when called this way! The problem, as far as I can tell, is that @a may look like a Seq or an Array, it's still actually an iterator. That means the first time you use it for something like, say, +@a, it is completely consumed! Then the next time you access it, there's nothing left. This is definitely not what is expected...

Update: Just realized that without "Perl 6", Ironman didn't pick up the previous post or this one. Time to edit...

E03 in Master

So, I thought porting my E03 script to run on the current Rakudo master would make a good topic for a post. So, what did I have to change?

1. The code wouldn't parse with the metaoperators in it. So they had to go. Sob!

2. Symbol '$is_active_bit' not predeclared. Okay, replace constant with my.

3. No applicable candidates found to dispatch to for 'subst'. Well, that never actually worked properly in alpha, either. Easy enough to replace this with map and a new sub.

4. Method 'e' not found for invocant of class 'Str'. Ack. I just commented out these calls (I presumed :s wouldn't work either).

5. := binding of variables not yet implemented. Switching this to = --- errr --- breaks the code. Guess I'll have to fix that in a minute.

6. Method 'count' not found for invocant of class 'Perl6MultiSub'. Blast. This was triggered by my new code
reduce(&infix:<+>)
. Easy enough to change it to reduce({ $^a + $^b }).

7. Cannot reduce() empty list. Arrrgh! Turns out duplicating what [+] does is harder than it looks. I added a sub sum to cleanly check for the empty list.

8. Nominal type check failed for parameter '@a'; expected Positional but got GatherIterator instead. Oh, no. This is some sort of internal issue showing through, and I have no idea how to fix it.

Guess I will stop there for now, because I don't know how to go further.

sub add_trailing_slash_if_needed($path) {
$path ~~ / \/ $ / ?? $path !! $path ~ '/';
}

sub load_data($filename, $version = 1, *@dirpath is copy) {
@dirpath = './', 'peter' unless +@dirpath;
@dirpath .= map({ add_trailing_slash_if_needed($_) });
say @dirpath.join("\n");

my %data;
for @dirpath -> $prefix {
my $filepath = $prefix ~ $filename;

# if ($filepath ~~ :e and 100 < ($filepath ~~ :s) <= 1e6)
{
say "Trying to open $filepath";
my $fh = open($filepath, :r)
or die "Something screwy with $filepath: $!";
my ($name, $vers, $status, $costs) = $fh.lines(4);
next if $vers < $version;
$costs = [split /\s+/, $costs];
%data{$filepath} = {};
%data{$filepath}<name vers stat costs rest> =
($name, $vers, $status, $costs, $fh.slurp);
say "$filepath done";
$fh.close;
}
}
return %data;
}

sub save_data(%data) {
for %data.kv -> $filepath, $data {
say "saving $filepath";
my $fh = open($filepath, :w)
or die "Something screwy with $filepath: $!";
$fh.print: ($data.<name vers stat>, ~($data.<costs>), $data.<rest>).join("\n");
$fh.close;
}
}

# I've no idea what this sub was supposed to do, so let's stick with something really
# simple for the moment.
sub amortize($a) {
$a;
}

sub sum(@a) {
+@a ?? @a.reduce({ $^a + $^b }) !! 0;
}

my %data = load_data(filename=>'weblog', version=>1);
my $is_active_bit = 0x0080;
for %data.kv -> $file, $data {
say "$file contains data on { $data<name> }";
$data<stat> +^= $is_active_bit;

# my @costs := $data<costs>;
my @costs = $data<costs>;
my $inflation = 0;
$inflation = prompt 'Inflation rate: '
until $inflation > 0;

# @costs = (@costs >>*>> $inflation).sort({ amortize($_) });
@costs = @costs.map({ $_ * $inflation }).sort({ amortize($_) });

# say "Total expenditure: { [+] @costs }";
# say "Major expenditure: { [+] @costs.grep({$_ >= 1000}) }";
# say "Minor expenditure: { [+] @costs.grep({$_ < 1000}) }";

say "Total expenditure: { sum(@costs) }";
say "Major expenditure: { sum(@costs.grep({$_ >= 1000})) }";
say "Minor expenditure: { sum (@costs.grep({$_ < 1000})) }";
say "Odd expenditures: { @costs.map(-> $a, $b { $a }) }";
}

# save_data(%data, log => {name=>'metalog', vers=>1, costs=>[], stat=>0});
save_data(%data);


Update: Adding the words "Perl 6" in text so Ironman picks it up. Sigh.

Saturday, February 20, 2010

E03 Second Stab

As usual, masak++ has some brilliant stuff in his take on the E03 challenge, which I am shamelessly borrowing for my second version. On the other hand, I think he has done himself a disservice by not making sure his code actually works in Rakudo, as I see several dodgy spots and an entire missing function in his version.

So, some notes on masak's code:
1) His default value for @dirpath is wonky. First, he seems to be assigning @std_dirpath to @last_dirpath if the former is undefined. Second, neither of those variables is actually declared. Third, he's left off the '.' case, which is the one that actually works.

2) On the plus side, he has a working regular expression for the subst, and includes the assignment operator I forgot (in my comment version). On the minus side, the right hand side of the substitution is bad -- it works in theory, but does not work in practice in Rakudo alpha (nor master, so far as I know).

3) I'm amused that he used comb instead of the original's split. Both lines have the exact same effect, so far as I know. Arguably his has a little more style than mine.

4) I'm interested that he didn't notice that %data{$filepath}{"filepath"} = $filepath is kind of redundant.

5) Kudos for remembering how to declare a constant. But without the seek method, @StartOfFile is never actually used in the code.

6) His save_data is kind of drastically more complicated than mine, because he didn't just read the rest of the file in load_data and store it. He also uses lines instead of slurp, which makes writing out the data a bit more complicated as well. (Are those .list statements really necessary?)

7) He relies on the E03's explanation that a Str converted to Num will return NaN if it is not a valid number. That's certainly not true of Rakudo alpha. I'm not clear if it's true in the Perl 6 spec or not. (Of course, I skipped that bit altogether...)

So here's my second version. I cribbed a few things from masak's version, and added an arbitrary second default directory so I could make sure that it handled having more than one file.

sub load_data($filename, $version = 1, *@dirpath is copy) {
@dirpath = './', 'peter/' unless +@dirpath;
@dirpath>>.=subst(/(<-[/]>)$/, {"$1/"}); # doesn't actually work in Rakudo alpha
say @dirpath.join("\n");

my %data;
for @dirpath -> $prefix {
my $filepath = $prefix ~ $filename;

if ($filepath ~~ :e and 100 < ($filepath ~~ :s) <= 1e6) {
say "Trying to open $filepath";
my $fh = open($filepath, :r)
or die "Something screwy with $filepath: $!";
my ($name, $vers, $status, $costs) = $fh.lines(4);
next if $vers < $version;
$costs = [split /\s+/, $costs];
%data{$filepath} = {};
%data{$filepath}<name vers stat costs rest> =
($name, $vers, $status, $costs, $fh.slurp);
say "$filepath done";
$fh.close;
}
}
return %data;
}

sub save_data(%data) {
for %data.kv -> $filepath, $data {
say "saving $filepath";
my $fh = open($filepath, :w)
or die "Something screwy with $filepath: $!";
$fh.print: ($data.<name vers stat>, ~($data.<costs>), $data.<rest>).join("\n");
$fh.close;
}
}

# I've no idea what this sub was supposed to do, so let's stick with something really
# simple for the moment.
sub amortize($a) {
$a;
}

my %data = load_data(filename=>'weblog', version=>1);
constant $is_active_bit = 0x0080;
for %data.kv -> $file, $data {
say "$file contains data on { $data<name> }";
$data<stat> +^= $is_active_bit;

my @costs := $data<costs>;
my $inflation = 0;
$inflation = prompt 'Inflation rate: '
until $inflation > 0;

@costs = (@costs >>*>> $inflation).sort({ amortize($_) });

say "Total expenditure: { [+] @costs }";
say "Major expenditure: { [+] @costs.grep({$_ >= 1000}) }";
say "Minor expenditure: { [+] @costs.grep({$_ < 1000}) }";
say "Odd expenditures: { @costs.map(-> $a, $b { $a }) }";
}

# save_data(%data, log => {name=>'metalog', vers=>1, costs=>[], stat=>0});
save_data(%data);

Friday, February 19, 2010

E03 First Stab

Well, this one is weird. The original program strikes me as almost non-sensical, and is missing a key subroutine, amortize. It also features a lot of Perl 6 wildly changed since it was written. Still, here's a stab at what I think it is trying to do.

sub load_data($filename, $version = 1, *@dirpath is copy) {
@dirpath = './' unless +@dirpath;
# @dirpath>>.subst(/c/, {$1/});

my %data;
for @dirpath -> $prefix {
my $filepath = $prefix ~ $filename;

if ($filepath ~~ :e and 100 < ($filepath ~~ :s) <= 1e6) {
say "Trying to open $filepath";
my $fh = open($filepath, :r)
or die "Something screwy with $filepath: $!";
my ($name, $vers, $status, $costs) = $fh.lines(4);
next if $vers < $version;
$costs = [split /\s+/, $costs];
%data{$filepath} = {};
%data{$filepath}<name vers stat costs rest> =
($name, $vers, $status, $costs, $fh.slurp);
say "$filepath done";
$fh.close;
}
}
return %data;
}

sub save_data(%data) {
for %data.kv -> $filepath, $data {
say "saving $filepath";
my $fh = open($filepath, :w)
or die "Something screwy with $filepath: $!";
$fh.print: ($data.<name vers stat>, ~($data.<costs>), $data.<rest>).join("\n");
$fh.close;
}
}

# I've no idea what this sub was supposed to do, so let's stick with something really
# simple for the moment.
sub amortize($a) {
$a;
}

my %data = load_data(filename=>'weblog', version=>1);
my $is_active_bit = 0x0080;
for %data.kv -> $file, $data {
say "$file contains data on { $data<name> }";
$data<stat> +^= $is_active_bit;

my @costs := $data<costs>;
my $inflation;
# while my $inflation = prompt('Inflation rate: ')
# print "Inflation rate: " and $inflation = +<>
# until $inflation != NaN;
$inflation = 1.2;

@costs = (@costs >>*>> $inflation).sort({ amortize($_) });

say "Total expenditure: { [+] @costs }";
say "Major expenditure: { [+] @costs.grep({$_ >= 1000}) }";
say "Minor expenditure: { [+] @costs.grep({$_ < 1000}) }";
say "Odd expenditures: { @costs.map(-> $a, $b { $a }) }";
}

# save_data(%data, log => {name=>'metalog', vers=>1, costs=>[], stat=>0});
save_data(%data);


Some random notes:
1) The original code references @last_dirpath and @std_dirpath but nowhere are they defined, nor would they ever get used. I have simply taken them out.

2) The original code used a hyper-smartmatch with a s// substitution. To the best of my knowledge, that's never worked in Rakudo... um, almost said master, but I guess it's called "alpha" now. I tried replacing it with a hyper-.subst, but I couldn't get that to parse, either. I just skipped over this bit.

3) The original tried to create a read/write filehandle, stash it after reading, store it, and re-write the file entirely using seek and truncate. Neither of those functions exist in alpha as far as I can tell, so I just rewrote it to read the entire file when loading, and then open a new file in write mode when saving. This seems more sensible anyway, and has the benefit of working.

4) The original used ofs to set the output field separator. That's deprecated, so I just used a simple join statement to do the same thing.

5) I couldn't figure out how to get the code to get inflation from a user prompt, so I just hard-coded inflation to be... um... 20%. That seems kind of high now that I think about it. (And what is it with these old example programs and reading data from a prompt? That's something I've never wanted to do in more than a decade of Perl 5 programming...)

6) The four-line hyper-times, map, sort, map operation becomes a simple one-liner in modern Perl 6.

7) For some reason, the "Total expenditure" line prints the individual contents of @costs rather than the sum. No clue what's going on there.

8) Simple greps instead of the original's custom filters.

9) Use a simple map to get every other cost, because 1, 3 ... * doesn't work in alpha.

And that's it, I think. It's kind of weird and ugly, but I think it does do what it's supposed to. (I mean, it does do something, and I think that what it does is what the original code intended. But given the oddness of the original, who knows?)

I look forward to seeing what masak does with this one...