Perl6でマンデルブロ集合さんを描く

複素関数論のレポートで「マンデルブロ集合さんを描け」というのがあったので、折角だからとPerl6で書いてみました。当然モジュールなんて有るわけないので、フルスクラッチですね、大した量ではありませんが。コードは以下の通り。

実行結果

少なくともRakudo Star 2011.04で動作しますが、最新版のRakudoではpack関数が実装されてないらしく動かないようです。


今回改めて思ったのですが、Rakudo Star遅過ぎですね…。レポート向けに解像度200x200/判定までの回数:20回で出力したのですが、驚くべきことに3時間40分掛りました。コードを見れば解ると思いますが、これでも計算回数を抑えるのに酷いコードを書いています。古い実装にあれこれ言っても仕方ないのですが、気になったのはオブジェクトの生成と、関数呼び出しのコストの高さです。当初の設計では、座標を渡してマンデルブロ集合に属すかどうかを判定する関数を呼んでいたのですが、ピクセル分呼ぶと酷いことになってしまったのでインライン化しました。また、ループの中で変数を宣言すると死ぬほど時間掛かるので使い回してます。これだけで大分見通しの悪いコードが出来上がりました。あとはIO.writeが非常に遅いです、こればかりはどうしようもありませんが…。


ここまで汚いコード書いてるとPerl6で書いてる意味があるのか怪しくなってきますね…。


あと、ビットマップを出力できるモジュールが見当たらなかったので適当にBitmapクラス作りました。こちらはGitHubに上げましたが…あまりに酷い出来です、こんなのでよければ好きに使ってください。open関数に:binオプション渡すのを忘れてて二時間無駄にしたなんて言えない…。


https://github.com/VienosNotes/p6-Bitmap


使い方は単純で、ファイル名を渡してnewして、画像のサイズを渡してmake_headerした後でprint_headerを呼び、あとは一列分のピクセル情報(青0~255, 緑0~255, 赤0~255, 青, 緑, 赤...)を上から順にBitmap.push_lineに突っ込むだけです。4バイト境界のパディングは必要ありません。正しくない長さを書き込んだり、必要以上に回数呼んだりした場合の動作は保証しません。いや、どんな場合でも何も保証しませんけど。デストラクタの書き方が良く分からなかったので、最後に手動でBitmap.closeを呼んであげてください。


今回ので解ったことは、「Perl6はまだまだ未完成、でも普通に他の言語で出来る事は出来なくもない」ってところですかね。Groovyの本で「もはやGroovyは単なる玩具では無くなった」と言い切っていたのが少し羨ましくもありますが。

    • -

追記

組み込みのpack関数を使わずに、自分でBufオブジェクトを作るようにしたら若干高速に動作するようになりました。関数呼び出しの回数も減りましたし、長大なテンプレートのパースをしないぶん速くなっていると思います。

$!file.write(pack $!template, |@line);
#この部分を以下のように変更

my $buf = Buf.new;
$buf.contents.push(|@line);
$!file.write($buf);

例のお題をPerl6で解いてみる

Link> お題:文字列を先頭から見て同じところまで除去 - No Programming, No Life


という企画に実解析さんが参加しておられたので、私もPerl6で書いてみようかなと。


Link> お題:文字列を先頭から見て同じところまで除去 - はっ!透ける!―黙々とHaskellをやるダイアリー


一晩悩んで書いた結果、こんな感じ。

sub func (*@_) {
    return @_>>.substr(([>>eq>>] @_.map({[.comb]})).map({.Int or last 0}).reduce(&infix:<+>));
}

実行してみましょう。

say func("aaabca", "aaefga", "aaxyza").perl;
say func("1aa", "2aa", "3aa").perl;
say func("aaa", "aaa", "aaa").perl;
("abca", "efga", "xyza")
("1aa", "2aa", "3aa")
("", "", "")

ちゃんと動いてるようですね。
実はこの関数、空文字列を渡すとreduce周りで死んじゃったりするので、要求定義を全て満たしているかは微妙です…

    • -

実は、最初にこのお題を見た時「これはHyperoperator使うと短く書けるなー」という天啓を受けたので挑戦してみたんですが、予想以上にスマートに書けたので驚いています。HyperopratorとReduce Metaoperatorのパワフルさを垣間見た一日でした。

Twitterのライブラリを書きたい

まだPerl6実装のTwitterクライアントが存在しないと聞いて、ゴリゴリ実装中です。完成までにまだまだ時間は掛りそうですが、完成したらGitHubで公開しようと思ってます。

書いてる上で思った事、詰まった事を何点か。

モジュール無い

はい、致命的ですね。OAuthの署名に使うHMAC-SHA1というダイジェストの方式があるのですが、まずSHA1を計算するモジュールがありません。もちろんHMAC署名を生成するモジュールもありません。これが無いとOAuthはどうにもならないので、いまはココを書いています。

SHA1

Pure Perl6でSHA1ダイジェスト関数を実装すると死ぬほど遅いのは目に見えてるので、/tmp以下にメッセージをダンプして、sha1sum(MacOSXだとshasum)に投げています。OOっぽいインターフェースと手続きっぽいインターフェースを両方準備したせいで、かなり行儀悪いコードになっている気が…。add(),addfile(),hexdigest()などPerl5のDigest::SHA1と同じインターフェースを用意しています。

HMAC-SHA1

これもPerl5のDigest::HMAC_SHA1を意識した設計にしているのですが…。

class Digest::SHA1 {
      method add (Str $s) { ... }
      method addfile (Str $file) { ... }
      method clear () { ... }
      multi method hexdigest () { ... }
      multi method hexdigest (Str $s) { ... }
}

role Digest::HMAC {
     method new (Str $key) { ... }
     multi  method hexdigest () { self.SUPER::hexdigest()を使って計算 }
     multi  method hexdigest (Str $s) { self.SUPER::hexdigest()を使って計算 }
}

理想としてはこんな感じ。コレが上手く書けるなら、任意のDigest::Hに関して以下のようにHMAC版クラスを作る事が出来ます。

class Digest::HMAC::H is Digest::H does Digest::HMAC { }
my $hmac-h = Digest::HMAC::H.new("key");

# もしかしたら以下のように書けるかも
# 少なくともRakudo Star 2011.04では動かない

my $hmac-h = (class Digest::HMAC::H is Digest::H does Digest::HMAC { }).new("key");

ただ、「理想」と言ったのは、現行のStar 2011.04ではnextsameやSUPER疑似パッケージが使えないため、継承元のオーバーライドされたメソッドを明示的に呼び出せないためです。なので、role Digest::HMACはhexdigest()という名前でなく、hmac-hexdigest()という名前でダイジェストを計算する仕様になっています。処理系が改善されれば、順次改良して行きたいですね。

ビット演算とか

Perl6にはビット列のようなクラスは標準ライブラリでは用意されていません。基本的にはInt型で問題は無いはずでしたが…。

HMAC-SHA1の署名を生成する際に、どうも排他的論理和が上手く計算できていないようなのです。いろいろ覗いてみた結果、Int型が512bitのデータを保持できずに桁溢れしている事が判明しました。仕様ではInt型は多倍長の整数を保持できるようになっているようですが、現行の実装では符号付き64bit整数までしか格納できません。これはParrotのデータ型に依存してるんじゃないかなーという所です。@uasiさんに教えて頂いた所によると「GMPのParrotバインディングが開発されているので完成すれば進展あるかも」とのことです。

infix:<~^>を使う事で文字列同士の排他的論理和を取る事が出来るのですが、生バイナリをそのままStrに突っ込むと"Invalid operation on binary string"と怒られてしまったり、良く無い事が色々起きてしまっていて悲しいので、BitStreamクラスを作るかなーというのを考えています。単純にIntリストのラッパークラスを作るだけ…だと良いのですけれども。

オマケ

ビット演算周りで得たノウハウを幾つか

# ASCII文字列を0/1のビット列へ
$str.comb>>.ord>>.fmt("%08b").join;

#逆
$bin.comb(/.**8/).map({("0b" ~ $_).chr}).join;

# 16進数を10進数に
("0x" ~ $hex).Int;

#逆
$dec.fmt("%x");

# 16進数列を文字列に
$hex.comb(/../).map({("0x" ~ $_).chr}).join;

# 16進数列をビット列へ
$hex.comb>>.map({("0x" ~ $_).fmt("%04b")}).join;

[Perl6]急にPugsが来たので@Ubuntu 11.04

この前、完全に忘却の彼方に眠っていたPugsの最新版が公開されていたので、導入してみることにしました。Macでは非常に問題が多く、一筋縄ではいかなかったので、Ubuntu上でPugsを導入してみます。

Macの問題

アーキテクチャの話はズブの素人なので、以下は単なる憶測です。間違ってたらごめんなさい。

  • Snow Leopardはライブラリとかをビルドする時に64bit版のバイナリしか作らないが、Pugsi386向けのバイナリを要求する
    • MacPortsで入れたモノは+universalオプションをつけるとうまくいくかも
    • i386版のlibperl.aの作り方は結局分からなかった
  • MacOSXのオレオレ仕様iconvが上手く動かない

もしMacで成功した人が居たら、何がマズかったのか教えてください…

準備するもの

以下のライブラリをapt辺りから入れておきます。

  • ghc6
  • cabal-install
  • libperl-dev
  • libncurses-dev

また、Pugs自体のソースコード(tar.gz)を以下のページからダウンロードして適当な場所に展開します。

http://hackage.haskell.org/package/Pugs

ソースコードの修正

何故かエラーでコンパイルが通らないので、以下の一行をコメントアウトします。根本的な解決にはなってないので、後々障害の原因となるかもしれません。Haskell分かる人はパッチとか書いても良いんじゃないでしょうか。

diff -ru pugs_orig//src/Pugs/AST/Internals/Instances.hs pugs_modified//src/Pugs/AST/Internals/Instances.hs
--- pugs_orig//src/Pugs/AST/Internals/Instances.hs		2011-05-22 23:35:05.000000000 +0900
+++ pugs_modified//src/Pugs/AST/Internals/Instances.hs		2011-05-22 23:34:29.000000000 +0900
@@ -210,7 +210,7 @@
 instance YAML Dynamic
 instance YAML ProcessHandle
 instance YAML Regex
-instance YAML Unique
+--instance YAML Unique
 instance YAML VComplex
 instance YAML VHandle
 instance YAML VOpaque

あとはcabalにオマカセ

cabalの使い方は全然分からないのですが、ググって出たのを適当にたどります。

$cd ./pugs_modified
$cabal update
$cabal configure
$cabal install

これで~/.cabal/bin以下にpugs, pugs-DrIFTという実行ファイルが生成されて完了です。
試しにバージョンの確認でもしてみましょう。

$./pugs -v
   ______                                                           
 /\   __ \                                                        
 \ \  \/\ \ __  __  ______  ______     (P)erl 6                
  \ \   __//\ \/\ \/\  __ \/\  ___\    (U)ser's           
   \ \  \/ \ \ \_\ \ \ \/\ \ \___  \   (G)olfing      
    \ \__\  \ \____/\ \____ \/\_____\  (S)ystem           
     \/__/   \/___/  \/___/\ \/____/                           
                       /\____/            Version: 6.2.13.15
                       \/___/    Copyright 2005-2010, The Pugs Contributors
--------------------------------------------------------------------
 Web: http://pugscode.org/           Email: perl6-compiler@perl.org 

PugsはRakudoと違って、開き括弧を入力すると閉じるまで待ってくれるので、複数行からなるクラス定義などが書きやすいです。

$./pugs
   ______                                                           
 /\   __ \                                                        
 \ \  \/\ \ __  __  ______  ______     (P)erl 6                
  \ \   __//\ \/\ \/\  __ \/\  ___\    (U)ser's           
   \ \  \/ \ \ \_\ \ \ \/\ \ \___  \   (G)olfing      
    \ \__\  \ \____/\ \____ \/\_____\  (S)ystem           
     \/__/   \/___/  \/___/\ \/____/                           
                       /\____/            Version: 6.2.13.15
                       \/___/    Copyright 2005-2010, The Pugs Contributors
--------------------------------------------------------------------
 Web: http://pugscode.org/           Email: perl6-compiler@perl.org 

Welcome to Pugs -- Perl6 User's Golfing System
Type :h for help.

Loading Prelude... done.
pugs> class hoge {
....> has Str $.foo;
....> method bar () {
....>  say $!foo;
....> }
....> }
method :($__SELF__ is rw is ref, %_) "$_" := "Scalar" #<Scalar:0xb6d3a131>
                                     "%_" := "Hash" #<Hash:0xb6d3b10d>
                                     "$__SELF__" := "Scalar" #<Scalar:0xb6d50041>
                                     "&?BLOCK" := "Sub" #<Sub:0xb702b3c1>
                                     "&?ROUTINE" := "Sub" #<Sub:0xb702b3c1> {"&bar" := "Method" #<Method:0xb6d46585>, "$_" := "Scalar" #<Scalar:0xb703e825>
                                                                                                                      "@_" := "Array" #<Array:0xb703e895>
                                                                                                                      "&?BLOCK" := "Sub" #<Sub:0xb702b3c1>
                                                                                                                      "&?ROUTINE" := "Sub" #<Sub:0xb702b3c1>}{Syn "block" {App &say (: 
                                                                                                                                                                                     Var "$!foo");
                                                                                                                                                                           Noop}}
pugs> my $h = hoge.new(foo => "FOO");
pugs> $h.bar;
FOO

メタクラス周りはまだ実装されていないようですが、簡単なスクリプトなら問題なく実行できそうですね。
次のPerl6勉強会の発表ネタとして、後でRakudoと実効速度のベンチマーク取って比較してみたいと思います。

MacOSXでYaTeX環境を整える

用意するもの

あらかじめ導入されているのが前提で話を進めますので、導入方法とかは各自で適当にググるなりしてください。

YaTeXの下準備

Mac環境をUTF-8で統一したかったので、YaTeX自体に少しだけ手を加えます。yatexlib.elの49行目(バージョンによって違うと思いますが)のリストに'(4 . *utf-8*)を追加してください。

TeXShopの下準備

同じくUTF-8のための準備です。

$cd ~/Library/TeXShop/bin/
$cp platex2pdf-euc platex2pdf-utf8
$cp ptex2pdf-euc ptex2pdf-utf8

YaTeXの設定

環境に合わせて.emacs.elあたりに記述してください。殆ど拾い物です。

;; YaTeX-mode init
(setq auto-mode-alist
      (cons (cons "\\.tex$" 'yatex-mode) auto-mode-alist))
(autoload 'yatex-mode "yatex" "Yet Another LaTeX mode" t)

(setq tex-command "~/Library/TeXShop/bin/platex2pdf-utf8"
      dvi2-command "open -a Preview"
      YaTeX-kanji-code 4
      section-name "documentclass"
      makeindex-command "mendex"
      YaTeX-use-AMS-LaTeX t
      YaTeX-use-LaTeX2e t
      YaTeX-use-font-lock t
      )

使い方

  • *.texファイルを開くとHookしてYaTeX-modeに入る
  • C-c sでsection補完、C-c bでbegin補完
  • C-c tでコンパイルとかのサブメニュー

3日目 - ファイル操作

この記事は”Perl6 Advent Calendar”エスパー和訳したものです。大意は合っていると思われるものの、内容は保証しません。


<原文>


3日目 - ファイル操作 (原文/Moritz)

Day 3 – File operations
By Moritz

ディレクト

Directories

(Perl5の)opendirなどとは違い、Perl6では指定したディレクトリ(デフォルトではカレントディレクトリ)に含まれるファイルのリストを返すdir関数があるだけです。ひとかけらのコードが、千もの言葉を話すのです(sp,e 結果は読みやすいように折り返されています)。

Instead of opendir and friends, in Perl 6 there is a single dir subroutine, returning a list of the files in a specified directory, defaulting to the current directory. A piece of code speaks a thousand words (sp,e result lines are line-wrapped for better readability):

    # in the Rakudo source directory
    > dir
    build parrot_install Makefile VERSION parrot docs Configure.pl
    README dynext t src tools CREDITS LICENSE Test.pm
    > dir 't'
    00-parrot 02-embed spec harness 01-sanity pmc spectest.data

dir関数はtestという名前付きオプション引数を持っており、結果をgrepするのに使います。

dir has also an optional named parameter test, used to grep the results

    > dir 'src/core', test => any(/^C/, /^P/)
    Parcel.pm Cool.pm Parameter.pm Code.pm Complex.pm
    CallFrame.pm Positional.pm Capture.pm Pair.pm Cool-num.pm Callable.pm Cool-str.pm

ディレクトリはmkdir関数を使ってmkdir('foo')のように作成します。

Directories are created with mkdir, as in mkdir('foo')

ファイル

Files

Perl6で最も簡単にファイルを読むにはslurp関数を使います。slurp関数はファイルの中身をStringとして返します。

The easiest way to read a file in Perl 6 is using slurp. slurp returns the contents of a file, as a String,

    > slurp 'VERSION'
    2010.11

もちろん古き良きファイルハンドルを使った方法も可能です。

The good, old way of using filehandles is of course still available

    > my $fh = open 'CREDITS'
    IO()<0x1105a068>
    > $fh.getc # reads a single character
    =
    > $fh.get # reads a single line
    pod
    > $fh.close; $fh = open 'new', :w # open for writing
    IO()<0x10f3e704>
    > $fh.print('foo')
    Bool::True
    > $fh.say('bar')
    Bool::True
    > $fh.close; say slurp('new')
    foobar

ファイルテスト

File tests

ファイルの存在や種類をテストするにはスマートマッチング(~~)を用います。こんな風に:

Testing the existence and types of files is done with smartmatching (~~). Again, the code:

    > 'LICENSE'.IO ~~ :e # does the file exist?
    Bool::True
    > 'LICENSE'.IO ~~ :d # is it a directory?
    Bool::False
    > 'LICENSE'.IO ~~ :f # a file then?
    Bool::True

とても簡単です。

Easy peasy.

  • File::Find

File::Find

普通の方法で物足りない時は、モジュールが便利です。File::Find(File::Toolsパッケージに含まれています)はディレクトリツリーを横断して、欲しいファイルを探し出して見つかったファイルの遅延リストを生成してくれます。File::FindはRakudo Starに付属していますし、あなたが素のRakudoを使っているならneutro(訳注:Simple Module Installer 詳しくはここを参照のこと)を使って簡単にインストールすることができます。

When the standard features are not enough, modules come in handy. File::Find (available in the File::Tools package) traverses the directory tree looking for the files you need, and generates a lazy lists of the found ones. File::Find comes shipped with Rakudo Star, and can be easily installed with neutro if you have just a bare Rakudo.

使い方の例ですか?分かりました。find(:dir, :type, :name(/foo/))はt/dir1という名前のディレクトリの中にあるファイルの内、/foo/という正規表現にマッチしたファイルの遅延リストを返します。気をつけないといけないことは、このリストは単なる文字列ではないということです:そのオブジェクトは(文字列コンテキストでは)フルパスを文字列化したものになりますが、単なる文字列だけでなく自分自身のファイル名(name)と、所属しているディレクトリ(dir)へのアクセサも提供します(訳注:Rakudo Star 2010.11では未実装。単なる文字列が返ります)。更に詳しい情報についてはドキュメントを参照してください。

Example usage? Sure. find(:dir, :type, :name(/foo/)) will generate a lazy list of files (and files only) in a directory named t/dir1 and with a name matching the regex /foo/. Notice how the elements of a list are not just plain strings: they’re objects which strinigify to the full path, but also provide accessors for the directory they’re in (dir) and the filename itself (name). For more info please refer to the documentation.

便利なイディオム

Useful idioms

新しいファイルを作る

Creating a new file

    open('new', :w).close
"無名"ファイルハンドル

"Anonymous" filehandle

    given open('foo', :w) {
        .say('Hello, world!');
        .close
    }

2日目 - コマンドラインとMAIN関数の交流

この記事は"Perl6 Advent Calendar"エスパー和訳したものです。大意は合っていると思われるものの、内容は保証しません。


<原文>


二日目 - コマンドラインとMAIN関数の交流 (原文/Moritz)

Day 2 – Interacting with the command line with MAIN subs
By Moritz

Unix環境では、多くのスクリプトコマンドラインから引数やオプションを受け取ります。Perl6では、とても簡単にこれらを受け取ることができます。

In Unix environment, many scripts take arguments and options from the command line. With Perl 6 it’s very easy to accept those:

    $ cat add.pl
    sub MAIN($x, $y) {
        say $x + $y
    }
    $ perl6 add.pl 3 4
    7
    $ perl6 add.pl too many arguments
    Usage:
    add.pl x y

ただMAINという名前の関数とシグネチャを書くだけで、自動的にコマンドラインパーサとコマンドライン引数のシグネチャの仮引数($xと$y)への束縛、さらに(呼び出された時にコマンドライン引数とシグネチャが合致しなかったなら)Usage Messageを得ることができます。

By just writing a subroutine called MAIN with a signature, you automatically get a command line parser, binding from the command line arguments into the signature variables $x and $y, and a usage message if the command line arguments don’t fit.

Usage MessageはUSAGE関数を別に定義することでカスタマイズができます。

The usage message is customizable by adding another sub called USAGE:

    $ cat add2.pl
    sub MAIN($x, $y) {
        say $x + $y
    }
    sub USAGE() {
        say "Usage: add.pl <num1> <num2>";
    }
    $ perl6 add2.pl too many arguments
    Usage: add.pl <num1> <num2>

MAIN関数をmulitとして定義することで、使い分けられる構文をいくつか定義できます。つまり定数を用いたディスパッチです:

Declaring the MAIN sub as multi allows declaring several alternative syntaxes, or dispatch based on some constant:

    $ cat calc
    #!/usr/bin/env perl6
    multi MAIN('add', $x, $y)  { say $x + $y }
    multi MAIN('div', $x, $y)  { say $x / $y }
    multi MAIN('mult', $x, $y) { say $x * $y }
    $ ./calc add 3 5
    8
    $ ./calc mult 3 5
    15
    $ ./calc
    Usage:
    ./calc add x y
    or
    ./calc div x y
    or
    ./calc mult x y

名前付きパラメータはオプションに相当します:

Named parameters correspond to options:

    $ cat copy.pl
    sub MAIN($source, $target, Bool :$verbose) {
        say "Copying '$source' to '$target'" if $verbose;
        run "cp $source $target";
    }
    $ perl6 copy.pl calc calc2
    $ perl6 copy.pl  --verbose calc calc2
    Copying 'calc' to 'calc2'

パラメータをBool型として宣言した場合は値を受け取りません。逆に言えばBool型の型制約が無ければ引数を取ります:

Declaring the parameter as Bool makes it accept no value; without a type constraint of Bool it will take an argument:

    $ cat do-nothing.pl
    sub MAIN(:$how = 'fast') {
        say "Do nothing, but do it $how";
    }
    $ perl6 do-nothing.pl
    Do nothing, but do it fast
    $ perl6 do-nothing.pl --how=well
    Do nothing, but do it well
    $ perl6 do-nothing.pl what?
    Usage:
    do-nothing.pl [--how=value-of-how]

要約すると、Perl6はサブルーチンのシグネチャとmultiを使うだけで、組み込みのコマンドラインパーサとUsage messageを提供してくれるということです。

In summary, Perl 6 offers you built-in command line parsing and usage messages, just by using subroutine signatures and multi subs.

上手く書けば、宣言文は今までになく簡単なものになるでしょう。

Writing good, declarative code has never been so easy before.