インターフェースとダックタイピングについてちょっと考えてみる


こんにちは!!
ヨシオリです!!


Java Advent Calendar -ja 2010 : ATND
の 29 日目になります!!


え?
クリスマスで終りじゃないの??
まぁ,いいじゃないですか,適当にダラダラ続けてもw


ちなみに ATND の URL のイベント ID 的なのが 11000 なのですごいですね!!
というわけで,個人的には Java の中でも 1,2 を争うくらい好きなインターフェースについて書きます!!

まずは WEB+DB PRESS Vol.60 をヨムノデス


WEB+DB PRESS Vol.60

WEB+DB PRESS Vol.60


これの特集1である「言語設計の基礎知識」の後ろの方に謝辞で僕の名前が乗ってます.
まぁ,実際は何かしたわけでもなく,いつものようにグダグダと脳内の事を垂れ流しながらチャットしてただけなのですが,そこで「(Java の)クラスは機能を持ちすぎじゃない?」的な話題になりました.
その部分は実際に記事になっているので読んでもらうとして,同じように「(Java の)インターフェースも機能を持ちすぎじゃない?」という話題になりました.
しかしそこはページの都合などで残念ながらカットになってしまったので,その時に会話した内容とかを少しまとめてみたいと思います.

まずは インターフェース を使った例


Java Advent Calendar なのでまずは Java です.

package org.yoshiori;

interface Taichi {
	void gyappa();
}

class WarufuzakeTaichi implements Taichi{

	final String text;
	
	public WarufuzakeTaichi(String text){
		this.text = text;
	}
	
	@Override
	public void gyappa() {
		System.out.println(text);
	}
	
}
public class Test {
	
	public static void kickAss(Taichi taichi){
		taichi.gyappa();
	}
	
	public static void main(String... args){
		kickAss(new WarufuzakeTaichi("ぎゃっぱぎゃっぱ"));
	}
	
}

ここでは Taichi というインターフェースは gyappa というメソッドを実装してね! という感じになっています.
これがインターフェースの機能の一つですね.
なので,その Taichi を実装する WarufuzakeTaichi は gyappa メソッドを実装しています.
そして kickAss メソッドでは インターフェースである Taichi を引数に取っているので,そこに WarufuzakeTaichi を渡して gyappa メソッドを呼びだしてるわけですね!


さて,その WarufuzakeTaichi なのですが, gyappa というメソッドを実装しているという事の他に Taichi という型の情報も持ってしまっています.
これが悪い事かどうかは別にしても,あるメソッドを実装している事を保証したいだけなのに型の情報も付いてきてしまっていると考えられます.
では次にダックタイピングを見てみましょう.

次に ダックタイピング を使った例


今度はダックタイピングの例です.
なるべく Python を知らない人でもわかりやすく書いたつもりです.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

class WarufuzakeTaichi(object):

    def __init__(self,text):
        self.text = text
    
    def gyappa(self):
        print self.text


if __name__ == '__main__':

    def kick_ass(taichi):
        taichi.gyappa()

    kick_ass(WarufuzakeTaichi(u'ぎゃっぱぎゃっぱ'))

ここではインターフェース的なものは存在せずに kick_ass メソッドは引数で渡されたオブジェクトの gyappa メソッドを呼び出しています.
WarufuzakeTaichi クラスは gyappa メソッドを持っているので,そのまま呼び出して実行できます.


ただし,これは引数に渡されるオブジェクトが gyappa メソッドを実装している事を想定して書かれているので,WarufuzakeTaichi が gyappa メソッドを実装していなかった場合は実行時にエラーになります.

AttributeError: 'WarufuzakeTaichi' object has no attribute 'gyappa'


ちなみに完全に蛇足ですが,Python では zope.interface というのがあったり,3(同時に 2.6 にも) から Abstract Base Classes(abc)というのが追加されてたりします.

Java でも ダックタイピング を使ってみる


さて,たまに Java はダックタイピングが出来ない的な事を言われたりしますが,やろうと思えば出来ます.
シンプルにメソッド名でリフレクションで取得して実行してみましょう.

package org.yoshiori;

import java.lang.reflect.Method;

class WarufuzakeTaichi{

	final String text;
	
	public WarufuzakeTaichi(String text){
		this.text = text;
	}
	
	public void gyappa() {
		System.out.println(text);
	}
	
}
public class Test {
	public static void kickAss(Object taichi) throws Exception{
		Method gyappa = taichi.getClass().getMethod("gyappa");
		gyappa.invoke(taichi);
	}
	
	public static void main(String... args) throws Exception{
		kickAss(new WarufuzakeTaichi("ぎゃっぱぎゃっぱ"));
	}

}


簡単ですね.
特殊なように見えるかもしれませんが,最近ではメソッド名ではなく,アノテーションを使用したりして,FW などで使われる事も多いと思います.
これだと,WarufuzakeTaichi は余計な型の情報は持ちませんが,逆に gyappa を実装している事も保証していないため,Python と同じく実行時にエラーになります.
Java なのに実行時にエラーが出るような実装はイヤですよね!コンパイラに教えてもらいたいですよね!
あと,なんか太一,太一書きすぎて少しイヤになってた.

Go のインターフェースをちょっと見てみる


さて,(Java では)インターフェースを使うと型の情報まで持ってしまうし,ダックタイピングにしてしまうと,実行時にしかエラーがわからないという感じでした.
しかし,コンパイルが必要な静的型付け言語で型の情報を持たないインターフェースを実現している言語もあります.
みなさんも良く知っている Go ですね!!
では,早速見てみましょう.

// -*- coding: utf-8 -*-

package main
import "fmt"

type Taichi interface{ 
    gyappa()
}

type WarufuzakeTaichi struct{
    Text string
}

func(this *WarufuzakeTaichi) gyappa(){
    fmt.Printf("%s\n",this.Text) 
}

func kickAss(taichi Taichi){
    taichi.gyappa()
}

func main() {  
    obj := new(WarufuzakeTaichi)
    obj.Text = "ぎゃっぱぎゃっぱ"
    kickAss(obj)
}


Go にはクラスが無いので,構造体で書いていますがやっている事は一緒です.
Taichi というインターフェースを作り,そこで gyappa というメソッドを実装する事を定義しています.
kickAss メソッドは引数に Taichi インターフェースを受けとります.
ポイントはプログラム中で明示的には WarufuzakeTaichi と Taichi インターフェイスとの間に関連付けを行っていない (コンパイラは行う)という事です.

gyappa メソッドは実装していますが,Java のように明示的に Taichi インターフェース の gyappa メソッドを実装しているわけではありません.
では実際に WarufuzakeTaichi で gyappa メソッドを実装しなかった時はどうなるかと言うと……

taichi.go:26: cannot use obj (type *WarufuzakeTaichi) as type Taichi in function argument:
*WarufuzakeTaichi does not implement Taichi (missing gyappa method)

というように"コンパイル時"にチェックしてくれます.
凄いですね!!

まとまってないまとめ


インターフェースは機能を持ちすぎじゃないのか? という話を最初にしました.それは

  • あるメソッドを実装している事を保証するという機能
  • そのインターフェースの型としての機能

というものを持ってしまっているからです.
(何度も言いますが,それが良いか悪いかは論じてません!!)


なので,逆にインターフェースを使わないダックタイピングの例を次にみました.


そして,最後に Go のインターフェースの説明をしました.
Go での例はどこかダックタイピングのようにも見えます.そうなのです,インターフェースとダックタイピングは別に同列にあるものでも無いので共存も出来るのです.
僕は Go の例を見た時にこれは面白いなぁと思いました.


最初に書いた WEB+DB PRESS Vol.60 の記事の話をしている時に id:nishiohirokazu
「Go のインターフェースには触れないの??」
と聞いたのですが,紙面の都合などによりカットされちゃったので,ここに勝手に纏めてみました.
個人的には Go のインターフェースは面白いのですが,逆に実装の方のクラスだけ読んでも,どのメソッドがインターフェースとして使われてるのかわからないという弱点もあると感じます.
記事にも書いてありましたが,何か正解があるわけでは無い事なので,結論もグダグダですw
ただ,今回は 3 種類の言語しか触れてないのに,インターフェースについて色々考える事が出来ました.
特集1の第1章で Matz が書いていた「サピア=ウォーフの仮説」ではありませんが,色々な言語を元に考えてみると考えが膨らんで楽しいという事が理解してもらえればなぁと思います.