HOP 4.4.4

Higher Order Perlのappend()

  • listをイテレータにする関数を作る
  • イテレータをいくつかリストに突っ込む
  • さっきのlistをイテレータにする関数を使ってイテレータの詰まったリストをイテレータに

イテレータのappendの完成。素晴らしい。この流れでイテレータのzipとかもやれそう。

ProductName Higher-order Perl: A Guide To Program Transformation
Mark Jason Dominus
Morgan Kaufmann Pub / ¥ 7,625 (2005-05-30)
通常24時間以内に発送

Gaucheのbeginはperlのdo

最後の式を評価して返す

perlでfunc {}

func {なんかコード} = func(sub {なんかコード})の略らしい

sub Testfunc (&){return $_[0]->();}
Testfunc {print "hello kzfm"};

これはうまく動くが(&)がないと

sub Testfunc {return $_[0]->();}
Testfunc {print "hello kzfm"};

下のようなエラーが出る。

Not a CODE reference at test.pl line 1.

デバッガ動かしてみると先にTestfunc {print "hello kzfm"}がブロックとして評価されて、その後Testfunc {return $_[0]->();}が評価されるので$_[0]がコードブロックじゃないといわれているようだ。

プロトタイプで(&)ってことはコードブロック受け取ってるってことだよな。無名関数ってコードブロックか、、、あーなんとなくわかってきた気が。やっと{}がlispの括弧に見えてきた。

perlの0e0は真だけど0

HOP3章終了。

"0e0"の使いどころがなかなか。

論理演算

$ "0e0" || 1
0e0
$ 0 || 1 
1

真と判定、で演算してみる。

$ "0e0" * 5
0
$ "0e0" + 5
5
$ "0e0" - 5
-5

数値だ

$ "0e0" == 0
1
$ "0e0" eq 0

論理演算で真を返すのに0

で、これがどう便利かというと

$cached{$key} ||= make_cache($key)

キャッシュに0が入ってる場合偽と判定されてしまい、キャッシュがあるのに再計算してしまうのだけど"0e0"を入れておくときちんとキャッシュが効くと。

ProductName Higher-order Perl: A Guide To Program Transformation
Mark Jason Dominus
Morgan Kaufmann Pub / ¥ 7,678 (2005-05-30)
通常24時間以内に発送

HOPではOrcish maneuverに絡めてあって、3章はこの部分が一番面白かった。4章はイテレータ、この章に100p近く割いてある。

splitのLIMITパラメータ

splitにはlimitパラメータがあることをHOP読んでて知った。

$ $t = "a b c d e"
$ split /\ +/, $t 
a
b
c
d
e
$ split /\ +/, $t , 2
a
b c d e

splitっていろいろできんじゃん。

HOP再開

5章まで読んだけど、(たいして理解してなかったので)もう一度最初から読んでみる。

ProductName Higher-order Perl: A Guide To Program Transformation
Mark Jason Dominus
Morgan Kaufmann Pub / ¥ 7,678 (2005-05-30)
通常24時間以内に発送

prefaceにこの本はSICPにインスパイアされて書いたでよって書いてあった。

Gaucheのdisplayとprintって

Gaucheのdisplayとprintってperlのprintとsayと似たような関係なんだろうか?と思った。

perlでcall-with-procedure

参考にしたサイト

継続渡しスタイルに載ってたcall-with-procedureをperlで。

sub call_with_procedure {
  my ($exp,$proc) = @_;
  $proc->($exp);
}

sub fact {
  my $n = shift;
  $n == 1 ? 1 : $n * fact($n-1);
}

print call_with_procedure(sub {2 * $_[0]}, sub {$_[0]->(fact(10))});

実行すると

$ perl curpr.pl 
7257600

継続ってのはgaucheだと外に向いているようなものを内側にひっくり返す感があってなんか奇妙な感じがするが、いくつか書いてみてから継続の説明を読むと、なんかフムフム感が得られる。だけど、それをperlでやろうとするとひどく混乱するのはなぜだろうか?

sub {$_[0]->(fact(10))}->(sub {2 * $_[0]})
= {2 * $_[0]}->(fact(10))
= 2 * fact(10)

書き下してみるとこんな感じか、、、、

最初のsub{}->()ってコードはautoboxを使えば動きそうな気がするが、、、

実際にやってみることにした。

use autobox;
use autobox::Core;

sub fact {
  my $n = shift;
  $n == 1 ? 1 : $n * fact($n-1);
}

print sub {$_[0]->(fact(10))}->(sub {2 * $_[0]});

無名サブルーチンが第一引数に無名サブルーチンを取ってこれが引数にfact10の結果を取ってそれを二倍するっていう処理が実行される(はず)

$ perl abtest.pl 
7257600

動いた!

call/ccの勉強してたのに、キモコードを動かすというあらぬ方向に行ってしまったが、ファーストクラスオブジェクトってのがなんだかわかったようなわからんようなホンワカした何かを得た感があったのでよしとしよう。

perlの継続の謎のおまじないがやたらと気になる

継続ブーム到来中。なんでも継続読みましたよ、何度も何度も読みまくりなのでなんども継続

もちろんperlでもやってみたくなるので、なんでも継続、Perl で。とか継続は力なり読みながら手を動かしてみた。

が、perlだと、クロージャの中

my $dummy = $cont; # なぜか必要みたい。

ってのが何故必要なのかわからないし、なんでそんなへんなのがそこにあるのかが、凄い気になるのでデバッガで追いかけた。

該当行を

#my $dummy = $cont; # なぜか必要みたい。

として perl -dで起動してみるといつまでたっても終わらない。なんか無限ループしてるっぽい。mとnの値がどんどんでかくなっていってるみたいだし、、、、。デバッガじゃなくて何が渡されてるのかprintしてみる。

 sub {
  my ($n) = @_;
  my $dummy = $cont; # なぜか必要みたい。
  print "n: $n\n";
  &leaf_count_cps ($tree->[1],
                sub {
                     my ($m) = @_;
                     print "m: $m\n";
                     $cont->($n + $m);
                    });
   });

とやってnとmを追いかけると、おまじない入りは

$ perl cont.pl # [["a", "b"], [["c", "d"], "e"]];
n: 1
m: 1
n: 2
n: 1
m: 1
n: 2
m: 1
m: 3
5

予想通りの挙動。

で、my $dummy = $contが評価されないようにしてみると

$ perl cont.pl# [["a", "b"], [["c", "d"], "e"]];
n: 1
m: 1
n: 2
m: 1
m: 3
n: 4
m: 1
m: 5
m: 7
...ずっと続く...

よくわからん。別に代入じゃなくて$contをprintしてもrefしても正常に実行されるので、クロージャの中に評価する式があることが重要みたいなんだけど、、、

山口家の逆襲->perl-解説->クロージャ

4:実体をそのまま覚えるためには、サブルーチン内でもそのレキシカル変数が使われていなくてはならない。 (サブルーチン内にその変数がなければ、実質サブルーチンはその変数を知らないのと同じコトになる)

これとか関係あるのだろうか?

goto使った継続だとおまじないがいらないってのもまた謎なんだよなぁ。

Text::CSV::Simpleでピボット

単純なピボットがしたかった。

use Text::CSV::Simple;
my $file = shift;

my $parser = Text::CSV::Simple->new;
my @list = qw/cl1 cl2 cl3 cl4 cl5/;
$parser->field_map(@list);
my @data = $parser->read_file($file);

my $pivdata = {};

for my $d (@data){
 for my $label (@list){
   push @{$pivdata->{$label}},$d->{$label};
 }
}

for my $label (@list){
 print join ",", @{$pivdata->{$label}};
 print "\n";
}