第126回 ダックタイピング と インタフェース

動的型付け言語と静的型付け言語における多態性

オブジェクト指向では、多態性ポリモーフィズム)という発想がでてくる。この多態性を実現する方法を今回は2つ紹介する。
Perlは動的型付け言語であり、ダックタイピングという手法で、多態性を実現できる。対して、静的型付け言語であるJavaでは上位の型を作ることで多態性を実現できる(今回はインタフェースを用いてみた)。


多態性を用いればif文がなくなるなどの恩恵を受ける事ができる。
(今回はこのif文がなくなる場面については説明はしない)


ダックタイピング

Perlでのタックタイピングを見てみよう。

 my $human = Human->new();
 my $duck  = Duck->new();
 my $dog   = Dog->new();

 $human->touch($duck);
 $human->touch($dog);

いま、このようにhumanがtouchすると、おのおのの動物が鳴くソースを書いてみる。
$duck(アヒル)は$human(人間)に触られると「ガーガー」と鳴き、
$dog (犬)は$human(人間)に触れると「ワンワン」と鳴くとする。


全体のソース:

use strict;

##########
# 人間
##########
package Human; 
sub new{
  my $self  = shift;
  my $class = ref($self) || $self;
  return bless {}, $class;
}

sub touch{
  my $self = shift;
  my $o    = shift;

  $o->say();
}

##########
# アヒル
##########
package Duck;
sub new{
  my $self  = shift;
  my $class = ref($self) || $self;
  return bless {}, $class;
}

sub say{
  print 'ガーガー', "\n";
}

##########
# 犬 
##########
package Dog;
sub new{
  my $self  = shift;
  my $class = ref($self) || $self;
  return bless {}, $class;
}

sub say{
  print 'ワンワン', "\n";
}


#########
# main
#########
package main;
sub {
 my $human = Human->new();
 my $duck  = Duck->new();
 my $dog   = Dog->new();

 $human->touch($duck);
 $human->touch($dog);
}->();

結果:

ガーガー
ワンワン


これは何がすごいかというと、

sub touch{
  my $self = shift;
  my $o    = shift;

  $o->say();
}

の $o->say()の$oが$duckならDuckのsayが呼ばれてる $oが$dogならばDogのsayが呼ばれているところである。


Javaは型を書かないといけない

Javaでは引数に受け取る型を書かないといけないので、DuckとDogを受け取ることができない。
Humanクラスのtouchメソッドに注目してほしい。

public class Human {

	public Human() {
		// ただのコンストラクタ
	}
	
	public void touch(Dog dog){ //型を決めないといけない
		dog.say();
	}

}
public class Duck {

	public Duck() {	
		// ただのコンストラクタ
	}
	
	public void say() {
		System.out.println("ガーガー");
	}
}
public class Dog {

	public Dog() {
		// ただのコンストラクタ
	}

	public void say() {
		System.out.println("ワンワン");
	}
}
public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Human human = new Human();
		Duck  duck  = new Duck();
		Dog   dog   = new Dog();
		
		human.touch(duck);
		human.touch(dog);
	}

}

結果:

コンパイルエラー

インタフェース

そこで、JavaではDuckとDog以外にさらに上位の概念であるAnimalという型を作ってやる。今回はこれをインタフェースで実現する。

public interface Animal {
	public void say();
}


DuckとDogはこのAnimalを実装し、
Humanのtouchメソッドは、Animal型を受け取るように書き換える。

public class Human {

	public Human() {
		// ただのコンストラクタ
	}
	
	public void touch(Animal o){ //型を決めないといけない
		o.say();
	}

}
public class Duck implements Animal{

	public Duck() {	
		// ただのコンストラクタ
	}
	
	public void say() {
		System.out.println("ガーガー");
	}
}
public class Dog implements Animal{

	public Dog() {
		// ただのコンストラクタ
	}

	public void say() {
		System.out.println("ワンワン");
	}
}
public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Human human = new Human();
		Duck  duck  = new Duck();
		Dog   dog   = new Dog();
		
		human.touch(duck);
		human.touch(dog);
	}

}

結果:

ガーガー
ワンワン

このように、明示的に型を書かないといけない言語では、
さらに上位の型を作らなければ、柔軟なことをしにくい。

第124回 サブルーチンの頭に&は普通つけない

Perlの謎(その1):変数等の頭文字って何を意味するの? - 燈明日記

『&』が付くシンボルは、サブルーチンで、実は『&』を頭に付けるより、尾に『()』をつけてサブルーチンを明示する方が多いです。

おせっかいな、トラックバックで大変申し訳ないのですが、
少しだけ補足をさせてください。


私も初めはサブルーチンの頭に&を付けていたのですが(正確にいうと付けるのか付けないのかすごく悩んでいた)、これはPerl4のときには必要でしたが、Perl5からは不要になりました。



私が教えてもらったときのものを引用します。
関数と演算子 - deq blog
Perl の 関数で &あっても なくても文脈で判断してくれると ありますが、、
あれも なんか納得いかないですねぇ 結局 どっちにすべきなんだと。。。


答え:関数呼び出しに単項前置演算子 & は使いません。

もともと,Perl 4ではビルトイン関数は & が不要,ユーザ定義関数の呼び出しには前に & をつけなければなりませんでした。しかし,Perl 5ではユーザ定義関数の呼び出しに & をつけないように変更になりました。しかし,Perl 4時代のプログラムも修正無しで動かしたいため,& をつけても関数呼び出しが行えるようになっています。しかしこれはすでに廃止された仕様なので,関数呼び出しの頭に & をつけるのは止めましょう。



ちなみにこの件は、

Perlベストプラクティス

Perlベストプラクティス

にも紹介されていました。


また、この話は、
第120回 Perlらしく書くための5つの極意 - bingo_nakanishiの他言語出身者のためのPerl入門
極意1 「組み込み関数のカッコは書かない」
と関係があり、カッコの有無だけで組み込み関数と自分が作った関数を区別できるというメリットがあります。



ということで、おせっかいなトラックバック失礼いたしました...

第125回 こういうのはどうだろうか

var str = /ここに名前/;
var as  = document.getElementsByTagName('a');

for(i=0; i<as.length; i++){
  if(    as[i].getAttribute('class') != null
      && /screen-name/.test(as[i].getAttribute('class')) 
      && str.test(as[i].childNodes[0].nodeValue)
  ){
    as[i].parentNode.parentNode.parentNode.style.display = 'none';
  }
}

twitterで、strに指定した人を非表示にする。
例えば、連投が続いていて、いやもうそれ読んだよ! 的なときに。



といってもJSの練習で書いただけです。

第122回 Javaはこんな風にも書けるんですな

継承していきなりnewできる

ソース:

public class Hoge {
   
	public static void main(String[] args) {
    	new Object(){
	    	public void m(){ System.out.println("こんにちは"); }
	    }.m();
	}
}

結果:

こんにちは

入れ子になったコレクション

ソース:

import java.util.ArrayList;
import java.util.HashMap;


public class Foo {


	public static void main(String[] args) {
		ArrayList a  = new ArrayList();
		HashMap   t  = new HashMap();
		ArrayList a2 = new ArrayList();
		
		a.add("aaa");       a.add("bbb");
		t.put("k1", a);
		
		a2.add(t);
		
		System.out.println(((ArrayList) (((HashMap) a2.get(0)).get("k1"))).get(0));
		
	}

}


結果:

aaa

第121回 chmodのためのls -l

ls -lを実行すると

$ ls -l
total 16
-rw-r--r--  1 bingo_nakanishi  staff    0  6 12 20:15 bar.txt
-r-x------  1 bingo_nakanishi  staff    0  6  3 23:12 foo.txt
-rwxr-xr-x  1 bingo_nakanishi  staff  432  6 12 21:07 ls_n.pl
-rwxr-xr-x  1 bingo_nakanishi  staff  423  6 12 21:04 ls_n2.pl

こんな感じで、権限が rwx-のどれかで表現されるわけですが、
chmodに与えるときの引数は数字にしてやらないといけない。

chmod 755 hoge.txt

これじゃあ、コピペできないではないか!


ということで、権限を数字であらわしてくれるPerlを書いてみました。

ソース

#!/usr/bin/perl

use strict;

my $h  = {'r'=>4, 'w'=>2, 'x'=>1};

my @ls = `ls -l`;
print shift @ls;

for(@ls){
  my $rwx = (split / /, $_)[0];
  my @tmp = split //, $rwx;

  
  my $pre = sub{ my $num = 0; for(@tmp[1..3]){ $num += $h->{$_} } $num; }->()
          . sub{ my $num = 0; for(@tmp[4..6]){ $num += $h->{$_} } $num; }->()
          . sub{ my $num = 0; for(@tmp[7..9]){ $num += $h->{$_} } $num; }->();

  print $pre, $_;
}

結果

$ ./ls_n.pl 
total 16
644-rw-r--r--  1 bingo_nakanishi  staff    0  6 12 20:15 bar.txt
500-r-x------  1 bingo_nakanishi  staff    0  6  3 23:12 foo.txt
755-rwxr-xr-x  1 bingo_nakanishi  staff  432  6 12 21:07 ls_n.pl
755-rwxr-xr-x  1 bingo_nakanishi  staff  423  6 12 21:04 ls_n2.pl

行の頭に数字で表示されました。

アルゴリズムの説明

-rw-r--r--  1 bingo_nakanishi  staff    0  6 12 20:15 bar.txt

の部分から まず、

-rw-r--r--

の部分を、 $rwxに代入。
$rwxを1文字ずつばらして、@tmpに代入。
$tmp[0]は、ファイルかディレクトリかを表す部分なので、そこは、見てはいけない。なので@tmp[1..3]と1からスタート。
各無名関数に、オーナー、グループ、その他の権限を計算してもらい、その結果を文字列として連結。





ん〜でも、これだと、ls -lにしか使えない.....

これだと ls -lにしか使えないのでパイプで渡してきたのに数字を付け加えるものも書いてみました。

ソース

#!/usr/bin/perl

use strict;

my $h  = {'r'=>4, 'w'=>2, 'x'=>1};

while(<>){
  my $rwx = q/(r|w|x|-)/ x 9;

  my $pre = '';
  if($_ =~ /^.$rwx/){
    $pre = sub{ my $num = 0; for my $q($1,$2,$3){ $num += $h->{$q}; } $num; }->()
         . sub{ my $num = 0; for my $q($4,$5,$6){ $num += $h->{$q}; } $num; }->()
         . sub{ my $num = 0; for my $q($7,$8,$9){ $num += $h->{$q}; } $num; }->();
  }
    

  print $pre, $_;
}

結果

$ ls -ltr | ./ls_n2.pl 
total 16
500-r-x------  1 bingo_nakanishi  staff    0  6  3 23:12 foo.txt
644-rw-r--r--  1 bingo_nakanishi  staff    0  6 12 20:15 bar.txt
755-rwxr-xr-x  1 bingo_nakanishi  staff  423  6 12 21:04 ls_n2.pl
755-rwxr-xr-x  1 bingo_nakanishi  staff  432  6 12 21:07 ls_n.pl

正規表現は、ほとんど書いた事がないので、
なにかもっとよい方法や、まずい点にお気づきになりましたら、
教えていただけるとうれしいです。

アルゴリズムの説明

先頭が任意の文字で、r|w|x|-の文字が9つ連続した場合は、各文字を$1〜$9に記憶(先頭の任意の文字は記憶しない)。
あとは、各無名関数に、オーナー、グループ、その他の権限を計算させ、文字として連結。



おまけ

ちなみに、

sub{ }->()

のところはなにも、無名関数を使わなくても

do{  }

でもよいですね。

  my $pre = do{ my $num = 0; for(@tmp[1..3]){ $num += $h->{$_} } $num; }
          . do{ my $num = 0; for(@tmp[4..6]){ $num += $h->{$_} } $num; }
          . do{ my $num = 0; for(@tmp[7..9]){ $num += $h->{$_} } $num; };

こんな感じで。

第120回 Perlらしく書くための5つの極意

極意1 組み込み関数のカッコは書かない

open my $file, '<', 'hoge.txt';
split //, $c;

組み込み関数と自分が作った関数を一発で見分けられるのは有意義だ。


それに、記号が多いと読み辛い!
Perlが標準で持っている関数くらい覚えているぜ!!
ということをアピールするくらいの勢いで、標準の関数の括弧はなしで!!!!

極意2 変数は必要になったら宣言

my $c = 1;


# $cに関する処理


my $d = 10;

# $dに関する処理

プログラム言語の中には、関数の先頭で変数を宣言しなければならない言語があるが、Perlはそんなことはない!!!

必要になったときに宣言するんだ!!!!!!
後で読んだときに、変数をすべて頭に入れる必要がなくなる!!!!!


極意3 つなげる

my @c = 1..3;
for(reverse @c){ print $_, "\n" }

reverseで配列を逆順にしているが、
その逆順を後で使う必要がないなら、このように書ける。
無駄な変数を用意して、人間の脳細胞に負担をかける必要はない!!
いらないものはどんどん省くんだ!!!!


さらに後置のforを使って、

my @c = 1..3;
print $_, "\n" for reverse @c;

printする 逆順した@cを!


と言う風に前から読みくだせるように書けるぞ!!
英語を後ろから訳すとか言わずに、前から読みくだすのと同じ理屈だ!!!
無駄な変数に代入する事なく、処理は流れるようにつなげていく。


極意4 数字と文字を自由自在に操る

my $c = 1;
my $d = 2;

print $c + $d, "\n";
print $c . $d, "\n";

Perlは文脈によって、変数の中身が数字とみなされるか、文字とみなされるかが変わる。
この文脈を頭に叩き込み、自由自在に数字と文字を使うのだ!!!!!


極意5 forに配列以外を回させる

for(1, 'a', 3, 'x', 'ee'){
 print $_, "\n";
}

forが回せるのは配列だけとは思ってはいけない。
このように、回したいヤツを列挙していけばよいのだ!!!!!



読みやすさには注意!!!

いくら、極意を守るからといっても読みにくくては意味がないぞ!

for(
    reverse
    sub{
      my @c =  @_;
      grep { $_ % 3 == 0 } map { $_ * 2} @c; 
    }->(1,3,5,9)
){
 print $_, "\n";
}

こんなのは読みにくいよ!!!!