Hatena::ブログ(Diary)

Yarukidenized:ヤルキデナイズド

Yarukidenized

2010-07-30

[]それで Perl 6 の何がすごいんだと思ったあなた。ここがすごいんです。 15:42 それで Perl 6 の何がすごいんだと思ったあなた。ここがすごいんです。を含むブックマーク

たとえばこんな。 Rakudo Star ではどれも実装済み。今すぐ使えます。

強化された正規表現

Perl 6 の公式なパーサ自体も Perl 6 の正規表現で書かれています。

……は置いといて。10行ちょっとで CSVパースする正規表現が書ける。クォートされた値にも複数行にまたがる値にも対応。


# CSV クラスは以下のものを修正して使っています
# http://github.com/masak/csv/blob/master/lib/Text/CSV.pm

# 正規表現をまとめた grammar (クラスの一種)
grammar CSV {
    regex TOP   { ^ <line> ** \n <empty_line>? $ }
    regex line  { <value> ** ',' }
    regex value {
        | <pure_text>
        | \s* \" <quoted_contents> \" \s*
    }
    regex quoted_contents { <pure_text> ** [ <[,]> | \s | '""' ] }
    regex pure_text       { [<!before <[",]>> \N]+ }
    regex empty_line      { \h* \n }
}

# 特定の正規表現にマッチした部分文字列をオブジェクトに変換するクラス
class Actions {
    method TOP($/)   { make $<line>.map: *.ast }
    method line($/)  { make $<value>.map: *.ast }
    method value($/) { make $<pure_text>.chars ?? ~$<pure_text> !! ~$<quoted_contents> }
}

my $csv = 'foo,bar
"quoted
value", "..."';

my $match = CSV.parse($csv, actions => Actions);

say $match.ast.perl;  # (("foo", "bar"), ("quoted\nvalue", "..."))

JSON パーサも50行弱

関数シグネチャ

引数の定義。今まで使えなかった反動か、やたら強力。


# 1引数の関数
sub foo($value) { say $value }
foo('bar');  # bar

# 関数に渡した配列は展開されない
my @a = 1, 2, 3;
my @b = 4, 5, 6;
sub add-first(@a, @b) { @a[0] + @b[0] }
say add-first(@a, @b);  # 5

# 展開することもできる
sub my-elems(*@args) { @args.elems }
say my-elems(@a, @b);  # 6

# もろもろ
sub mixed(
    Int $int,      # 型制約
    $inty as Int,  # Int に変換可能な値(Str 等)を Int として受け取る
    $positive where { $_ > 0 },  # 値の制約
    $ref is rw,    # 読み書き可能な引数(呼び出し元の値が変更される)
    @arr is copy,  # コピーされた引数
    $optional?,    # 省略可能な引数
    $default = 1,  # 省略可能な引数(デフォルト値つき)
    :$key,         # 省略可能な名前付き引数
    :$required!,   # 省略不可の名前付き引数
    :n($longname)  # 名前付き引数の別名
    --> Int        # 返り値の型
) {
    ...
}

# シグネチャの情報も読める
sub func($x, *@rest) { ... }
say &func.arity;  # 1   -- 必須な引数の個数
say &func.count;  # Inf -- 取れる引数の最大個数
say &func.signature.perl;  # :(Any $x, *@rest)

メタ演算子とハイパー演算子

メタでハイパーな演算子。既存の演算子に作用して新しい演算子を作る。

# Z (zip) メタ演算子
# ある演算子 op に前置して @a Zop @b こう使うと、
# @a[0] op @b[0], @a[1] op @b[1], ... こんな形に展開される
say ((1, 2, 3) Z+ (1, 2, 3)).join(', ');  # 2, 4, 6
say ((1, 2, 3) Z- (1, 2, 3)).join(', ');  # 0, 0, 0

# X (cross) メタ演算子
# @a Xop @b は
# @a[0] op @b[0], @a[0] op @b[1], ...,
# @a[1] op @b[0], @a[1] op @b[1], ... こうなる
say ((1, 2) X+ (3, 4)).join(', ');  # 4, 5, 5, 6

# R (reverse) メタ演算子
# $a Rop $b は $b op $a になる
say 'a' R~ 'b';  # ba

# [] リダクション演算子コンストラクタ
# [op] @a こう使うと
# @a[0] op @a[1] op @a[2] ... に展開される
say [+] 1, 2, 3;  # 6

say [<] 1, 2, 3;  # 1  -- 1 < 2 < 3 は正しいので真
say [<] 1, 1, 2;  # 0  -- 1 < 1 < 2 は正しくないので偽

say [max] 1, 9, 2, 3;  # 9

# [\op] @a のようにバックスラッシュを前置すると
# @a[0], @a[0] op @a[1], @a[0] op a@[1] op @a[2], ...
say [\+] 1, 2, 3;  # 136

# ハイパー演算子
# @a >>op<< @b は
# @a[0] op @b[0], @a[1] op @b[1], ... になる
# 左右のリストの長さは同じでなければならない
say (1, 2, 3) >>+<< (1, 2, 3);  # 246

# @a >>op>> @b とすると、 @b の長さが足りないとき値を反復して補う
(1, 2, 3, 4) >>+>> (0, 1);  # → (1, 2, 3, 4) >>+<< (0, 1, 0, 1)

# >> の向きは任意に変えてよい: >>+<<, <<+<<, <<+>> など

静的・動的型付け

型。あってもなくても。


# 普通の変数は何でも代入可能
my $var;
$var = 42;
$var = 'str';

# Int 型の変数
my Int $intvar;
$intvar = 42;
$intvar = 'str';  # エラー

クラスとロール、オブジェクトモデル

すべてはオブジェクト


class Human {
    has $!secret;      # インスタンス変数は名前の頭に ! がつく
    has $.name;        # インスタンス変数 $!name とアクセサをまとめて定義
    has $.task is rw;  # $!task と読み書き可能なアクセサを定義

    method run() {
        say 'Running';
    }
}

# メタクラスにクラスオブジェクトの情報を問い合わせる
say Human.^methods(:local).join(', ');  # run, name, task

# ロール。Java 等でいうところのインターフェース
role Sleeper {
    method sleep { say 'zzz...' }
}

# ロールの組み込み
class Man is Human does Sleeper {}
Man.new.sleep;  # zzz...

# ロールの動的組み込み
class Woman is Human {}
my $woman = Woman.new;
$woman does Sleeper;
$woman.sleep;  # zzz...

遅延評価リスト

無限の数も掌の上。


# 右端が無限大の Range はそのまま配列変数に代入できる
my @arr = 0..Inf;
say @arr[10];  # 10

# gather for 文で遅延評価リストを作成し、 munch メソッドで一部を取り出す
say (gather for 0..Inf { take $_ ** 2 }).munch(5);  # 0 1 4 9 16

マルチディスパッチ

いわゆるオーバーロード


multi sub double(Int $i) { $i * 2 }
multi sub double(Str $s) { $s ~ $s }

say double(3);     # 6
say double("hi");  # hihi

スマートマッチ

なんでもかんでも ~~ 演算子で比較。


42 ~~ 1..100;        # 範囲に入っていれば真
's' ~~ Str;          # クラスのインスタンスであれば真
Int ~~ Any;          # クラスが継承関係にあれば真
Array ~~ Positional; # クラスがロールを実装していれば真
2 ~~ { $_ % 2 };     # ブロックが真を返せば真(左辺値は $_ で参照できる)
'Mon' ~~ any('Sun', 'Mon', 'Tue');  # どれかと一致すれば真
$obj ~~ :hello;      # $obj.hello メソッドが真を返せば真
$obj ~~ :!hello;     # $obj.hello メソッドが偽を返せば真
$obj ~~ :ok & :!bad; # $obj.ok が真かつ $obj.bad が偽なら真
'abc' ~~ /\w+/;      # 正規表現にマッチすれば真

my $abc = 'abc';
$abc ~~ s/b/x/;      # これは比較ではなく置換
say $abc;            # axc

# given-when は内部でスマートマッチを使っている
given $obj {
    when $case  { ... }
    when $case2 { ... }
    default     { ... }
}

# 上の given-when 文はこれと等価
if    $obj ~~ $case  { ... }
elsif $obj ~~ $case2 { ... }
elsif $obj ~~ *      { ... }

ジャンクション

複数の値を持つ値。値とジャンクションの比較は複数のスレッドで並列に行われる。


1 ~~ any(1, 2, 3);  # 左辺値とジャンクションの値のどれかが一致すれば真
                    # 比較は並列実行される

1 ~~ all(1, Int);   # 左辺値とジャンクションの値のすべてが一致すれば真

# ほかに none(), one() ジャンクションもある
# 省略記法あり
# any(1, 2, 3) → 1 | 2 | 3
# all(1, 2, 3) → 1 & 2 & 3
# one(1, 2, 3) → 1 ^ 2 ^ 3

# ジャンクションとスマートマッチで条件分岐が簡潔に書ける
given $obj {
    when :!defined | 0 {  # → !$obj.defined || $obj ~~ 0
        say "$obj is undefined or zero";
    }
    when Int | Num | Str {  # → $obj ~~ Int || $obj ~~ Num || $obj ~~ Str
        say "$obj is a built-in object";
    }
    when MyObj & { .value > 3 } {  # → $obj ~~ MyObj && $obj.value > 3
        say "$obj is a MyObj and it's value is larger than 3";
    }
}

# ジャンクションに対する演算はジャンクションのすべての要素に作用する
1 + any(1, 2, 3);  # この演算は
any(2, 3, 4);      # このジャンクションを返す

# 関数でも同様
lc(any('A', 'B', 'C'));  # この関数は
any('a', 'b', 'c');      # このジャンクションを返す

# ユーザ定義の関数でも
sub bikkuri(Str $str) { $str ~ "!!" }
bikkuri(any('A', 'B', 'C'));  # これが
any('A!!', 'B!!', 'C!!');     # こうなる

演算子オーバーロード

演算子を定義。お好きなように。


class Man {
    has $!name;
    method Str() { $!name }
}

multi sub infix:<+>(Man $a, Man $b) { $a ~ " and " ~ $b }

say Man.new(name => 'Taka') + Man.new(name => 'Toshi');  # Taka and Toshi

イントロスペクション

クラスの中を覗いたり。


class Man {
    has $!name;
    method hello() { say "Hi, I'm $!name" }
}

my $man = Man.new(name => 'John');

# Man クラスが持つメソッドを調べる
say Man.^methods(:local).join(', ');  # hello

# Man クラスにメソッドを追加
Man.^add_method('yaa', method () { say "Yaa, boku ha $!name" });
say Man.^methods(:local).join(', ');  # hello, yaa

# 追加されたメソッドは既存のオブジェクトからも見える
$man.yaa;  # Yaa, boku ha John

# メソッドオブジェクトを使ってメソッド呼び出し
for Man.^methods(:local) -> $method {
    $man.$method;
}
# Hi, I'm John
# Yaa, boku ha John

# メソッド名でメソッド呼び出し
my $method-name = 'hello';
$man."$method-name";  # Hi, I'm John

カリー化

引数を一部だけ与える。


sub add($a, $b) { $a + $b }
my &add1 = &add.assuming(1);
add1(2);  # 3

say (1 + *)(2);  # 3

……正確にはカリー化ではなく部分適用とクロージャです。

ソフトな例外

try-catch-finally なんかいらない。柔軟な例外。


sub never-succeed() {
    fail 'Shippai';  # Failure オブジェクトを返して関数を抜ける
    return 1;  # ここには到達しない
}

my $failure = never-succeed();

# Failure オブジェクトは未定義値として扱われる
say $failure.defined;    # 0

# Exception オブジェクトを取り出せる
say $failure.exception;  # Shippai

# Failure オブジェクト は、 Perl 5 でいう未定義値に例外情報を持たせたものと考えてもいい



# でも try-catch-finally したい!というときは、try-CATCH-LEAVE
try {
    $failure.exception.throw;  # 例外を投げる
    CATCH {
        # このブロックは例外が投げられたときのみ実行される
    }
    LEAVE {
        # このブロックは例外の有無にかかわらず try を抜けるときに実行される
        # LEAVE ブロックは Rakudo Star 2010.07 では未実装
    }
}

# CATCH, LEAVE はフェーザーと呼ばれるもので、 try だけでなく任意のブロックの中に書ける
sub catch-it() {
    $failure.exception.throw;
    CATCH {
        say 'caught';
    }
}
catch-it();  # caught

複数行コメント

言いたいことはたくさんある。


#`{
    ここから
    ここまでコメント
}

#`{{ ← カッコは好きなだけ増やせるので……
   } ← こんなコメントを含んでも安心
}}

#`(((
   お好きなカッコでどうぞ 
)))

Whatever star

望み通りにふるまう star の力。


# 組み込み演算子や関数の一部は、 * を「何でも」「すべて」の意味で扱う
1..*;  # → 1..Inf

my @a = 1, 2, 3;
say @a[0..*];  # 123  -- 0番目から最後の要素まで取り出す
say @a[*-1];   # 3  -- 後ろから数えて1つ目の要素
               # Perl 5 の負値インデックス $a[-1] は廃止

@a.pick(*);  # 312  -- 全要素をランダムな順番で取り出す

# 項の位置に * を含む式はブロックを生成する
# 1 + * は { 1 + $_ } と同じ
my &add1 = 1 + *;
say add1(2);  # 3

my &mul = * * *;
say mul(3, 5);  # 15

say <a b c>.map: { $_.uc };  # ABC
say <a b c>.map: *.uc;       # ABC

Neko operator

にゃーん。


0 ^..^ 9;  # → 1 .. 8

punitanpunitan 2010/07/31 05:58 X メタ演算子のところが
say ((1, 2) Z+ (3, 4)).join(', ');
になってますよ

uasiuasi 2010/07/31 10:28 > say ((1, 2) Z+ (3, 4)).join(', ');
ありがとうございます。修正しました。