Perlでオブジェクト指向プログラミング

この記事はモバイルファクトリー Advent Calendar 2017の2日目の記事です。
1日目に引き続き、@carimaticsが担当します。

1日目の記事は『[Docker上でPerlのWebアプリケーションを動かしてみる](https://qiita.com/carimatics/items/b24b51a2027eb749d44b)』でした。

本記事では、Perlにおけるオブジェクト指向プログラミングの入門です。
オブジェクト指向プログラミング自体の入門ではありません。

# 開発環境

以下の環境で動作確認をしています。

* OS: macOS Sierra version 10.12.6
* Perl: perl v5.26.0
* Carton: carton v1.0.28
* Docker: Version 17.09.0-ce-mac35
    * Docker image: perl:5.26

本記事のコードは以下のコマンドで実行されるDockerコンテナで動かしました。

```shell-session
$ docker run -it perl:5.26 bash
```

# 準備

## Cartonのインストール

Cartonがなければインストールします。

```shell-session
$ cpanm Carton

(中略)

7 distributions installed

$ carton --version
carton v1.0.28
```

## 作業ディレクトリの作成

適当なディレクトリを作成してそこに移動します。
`/path/to/Work` となっていますが、**適宜読み替えてください**。

```shell-session
$ mkdir /path/to/Work
$ cd /path/to/Work
```

以下、本記事における「**作業ディレクトリ**」は、今作ったWorkディレクトリを指すとします。

## 依存モジュールのインストール

作業ディレクトリに以下の内容のcpanfileを作成します。

```shell-session:Work/cpanfile
requires 'Mouse';
```

依存モジュールをインストールします。

```shell-session
$ carton install

(中略)

Complete! Modules were installed into Work
```

# Perlでオブジェクト指向プログラミングを実現する

もともとPerlはオブジェクト指向言語**ではありません**。
しかし `bless` を駆使したり、CPANモジュールを利用したりすることでオブジェクト指向プログラミングが可能です。

本記事では、 `Mouse` というモジュールを用いてPerlにオブジェクト指向システムを導入します。

実行するスクリプトを `Work/script` 配下に、モジュールを `Work/lib` 配下に作成していきます。

`Work/script` 配下のスクリプトには、ファイルの先頭に以下の記述があるものとします。(冗長になるため、本文中では省略します)

```pl
use strict;
use warnings;
use utf8;
use feature qw/say/;
use lib 'lib';
```

## クラス
Mouseを用いると、クラスの定義をすることができます。
xy平面上の点を表す `Point` クラスを定義してみましょう。

```pl:Work/lib/Point.pm
package Point {
    use Mouse;

    has x => (is => 'ro', isa => 'Int');
    has y => (is => 'ro', isa => 'Int');
};

1;
```

`Mouse` モジュールを `use` すると、そのパッケージを `Mouse` クラスにすることができます。
`has name => parameters` の構文で、属性(インスタンス変数)を定義します。
`Point` では、 `x` 、 `y` 、という属性を定義しています。
パラメータはそれぞれ以下のような指定ができます。

* `is`
    * `ro` (read only)、 `rw` (read/write)、 `bare` (何もしない)
* `isa`
    * 変数に代入できる値の型を指定

今回は `x` 、 `y` は両方ともread only( `ro` )かつ整数( `Int` )型となります。

では、 `Point` クラスを使ってみましょう。

```pl:Work/script/point.pl
use Point;

my $p = Point->new(x => 3, y => 4); # インスタンス生成
say "x: ", $p->x;
say "y: ", $p->y;
```

Mouseクラスのインスタンスは `ClassName->new(parameters)` で作成します。
また、引数で属性の初期化をすることができます。

実行してみます。

```shell-session
$ carton exec perl script/point.pl
x: 3
y: 4
```

## 継承

Mouseにも継承の仕組みがあり、あるクラスのサブクラスを作成することができます。
では、例とした先に作成した `Point` に色の情報を持たせた `ColorPoint` クラスを作成します。

```pl:Work/lib/ColorPoint.pm
package ColorPoint {
    use Mouse;

    extends qw/Point/;

    has color => (is => 'ro', isa => 'Str');
};

1;
```

`extends` でスーパークラスを指定できます。
`Point` クラスに `color` 属性を持たせたクラスが出来上がりました。
実行してみましょう。

```pl:Work/script/color_point.pl
use ColorPoint;

my $cp = ColorPoint->new(x => 2, y => 3, color => 'red');
say "x: ",     $cp->x;
say "y: ",     $cp->y;
say "color: ", $cp->color;
```

```shell-session
$ carton exec -- perl script/color_point.pl
x: 2
y: 3
color: red
```


`isa` による型チェックでは、指定されたクラスのサブクラスでもパスします。
2つの `Point` 間の距離を測る `Ruler` クラスを作成します。

```pl:Work/lib/Ruler.pm
package Ruler {
    use Mouse;

    has p1 => (is => 'rw', isa => 'Point');
    has p2 => (is => 'rw', isa => 'Point');

    sub distance {
        my $self = shift;

        my $dx = $self->p2->x - $self->p1->x;
        my $dy = $self->p2->y - $self->p1->y;

        return sqrt(($dx**2) + ($dy**2));
    }
};

1;
```

これは酷いコードですね！
しかし今は許容して話を進めます。

`Ruler` クラスは `Point` クラスの属性 `p1` 、 `p2` を持ち、それらのユークリッド距離を計算しています。

```pl:Work/script/ruler.pl
use Ruler;
use Point;
use ColorPoint;

my $p1 = Point->new(x => 0, y => 0);
my $p2 = ColorPoint->new(x => 3, y => 4, color => 'blue');

my $ruler = Ruler->new();
$ruler->p1($p1);
$ruler->p2($p2);

say "distance: ", $ruler->distance();
```

属性への値のセットはコンストラクタでも可能ですが、 `$instance->attr(value)` でも可能です。(read onlyでなければ)

```shell-session
$ carton exec -- perl script/ruler.pl
distance: 5
```

`Ruler` の `p2` は `Point` クラスのインスタンスを持つことができますが、そのサブクラス( `ColorPoint` )を持つこともできることを確認しました。

# まとめ

Perlでもオブジェクト指向プログラミングができることを見ました。
`has` とか書くの面倒くさいですね。
この程度のコードであれば他の言語とそんなに変わらないのですが、オブジェクト指向プログラミングを言語レベルでサポートしているわけではないので、書いてて煩わしさを覚える場面は多々あります。
個人的な意見としては、オブジェクト指向プログラミングをしたいなら他の言語を選択すべきだと思ってます。

モバイルファクトリー Advent Calendar 2017は、3日目も引き続き @carimatics が担当します。
よろしくお願いします。