ラベル Java の投稿を表示しています。 すべての投稿を表示
ラベル Java の投稿を表示しています。 すべての投稿を表示

2010年11月8日月曜日

Haskell の日付型

Ruby, Python, Java の日付型

Java で日付型の値を扱う場合、何だかゴテゴテとしたコードを書く必要がある。これに対し、Ruby や Python の記述はシンプルでスッキリとしている。

例えば Ruby で `2010年4月1日’に対応した日付オブジェクトを使いたい場合、

require 'date'
puts Date.new(2010, 4, 1)

Python では、

import datetime
print datetime.date(2010, 4, 1)

ちなみに Java だと、

import java.util.Calendar;
import java.text.SimpleDateFormat;

public class TestDate {
    public static void main(String[] args){
	Calendar cal = Calendar.getInstance();
	cal.set(2010, Calendar.APRIL, 1);
	System.out.println
	    (new SimpleDateFormat
	     ("yyyy-MM-dd").format(cal.getTime()));
    }
}

うーん、ややこしい。 (+_+)

Java の日付時刻に関することは以下も参考に。

 

Data.Time

Haskell でも同じく Date 型がないかと探してみると、Data.Time の階層に日付を扱うためのモジュールがある。

の 4 つのモジュールに分かれており、上 3 つ はその名前通りの用途に用いることができそう。最後の LocalTiem モジュールの中には、日付を含まない時間を表す型が定義されていた。

(cf. 時刻を扱う - 特定の日付に属さない時間 )

 

日付から Day 型への変換

Data.Time.Calendar には日付に対応した Day 型が定義されている。

コンストラクタは、

ModifiedJulianDay {toModifiedJulianDay :: Integer} 

この型の説明には、

The Modified Julian Day is a standard count of days, with zero being the day 1858-11-17.

とある。 Modified Julian Day とは 「 ユリウス日(Julian Day)」 によると、

ユリウス日(ユリウス通日)とは紀元前4713年1月1日からの連続した通し番号の日数です。

ユリウス通日 – Wikipedia

数年にわたる2点の日数を計算するのに便利で、天文学年代学などで使われている。ユリウス通日では桁が多すぎるため、ユリウス通日から2400000.5を引いた修正ユリウス日(MJD)も広く使われている。

内部的にはこの数値が Day 型に使われているようだ。

 

fromGregorian 関数

日付からこの Day 型に変換するには、

fromGregorian :: Integer -> Int -> Int –> Day

この関数を使ってみる。

import Data.Time.Calendar
main = print $ fromGregorian 2010 4 1

 

toModifiedJulianDay 関数

fromGregorian 関数によって Day 型に変換された値は、セレクタ toModifiedJulianDay により修正ユリウス日を得られる。

フリーゲルの公式 によると、

例えば、2004年1月1日はy=2003、m=13、d=1なので

\lfloor 365.25 \times 2003 \rfloor + \lfloor 2003 / 400 \rfloor -  \lfloor 2003 / 100 \rfloor + \lfloor 30.59 ( 13 - 2 ) \rfloor + 1 - 678912
= 731595 + 5 − 20 + 336 + 1 − 678912 = 53005

となり、53005が修正ユリウス日となる。

確かめてみる。

Prelude> :m Data.Time.Calendar
Prelude Data.Time.Calendar> toModifiedJulianDay $ fromGregorian 2004 1 1
53005

 

グレゴリオ暦

それにしても Haskell では、Ruby や Python のようにシンプルに Date というような名前のコンストラクタではなく、fromGregorian というゴツイ関数名が付けられているのだろう?

Gregorian とは 「グレゴリオ暦」 のことを指し、

現在、日常使われているグレゴリオ暦は、1582年に制定された事実上の世界標準の暦です。 このグレゴリオ暦の前身はユリウス暦で、そのユリウス暦は、古代ローマ暦にエジプト 発祥の太陽暦を導入したものです。

日本では、1872年からグレゴリオ暦が採用されています。

普段意識せずに使っているのがグレゴリオ暦で、その前に使われていたのがユリウス暦だと。

なぜこの暦が使われるようになったかと言えば、グレゴリオ暦 - Wikipedia によると、

それまで用いられていたユリウス暦では、通常の年(平年)は1年を365日とし、4年に1回を閏年として366日とし、平均年を365.25日としていた。…

しかし太陽年は約365.2422日であるため、ユリウス暦の方式では1000年で約8日の誤差が生じる。これにより、比較的頻繁に補正することが必要であった。…

これに対して、新たに定められたグレゴリオ暦では、平年は1年を365日とし、4年に1回を閏年とするところまではユリウス暦と変わらないものの、さらに調整を加えて平均年を365.2425日とした。この調整とは「西暦紀元(西暦)の年数が100で割り切れてかつ400では割り切れない年は閏年としない[2]」というルールを加えることである。これはすなわち、ユリウス暦の方式では閏年とされる年であっても400年間に3回は閏年とせずに平年に戻すということである[3]

プログラムの入門書でよく見かける 「うるう年を判定するプログラム」 の判定の理屈は、このグレゴリオ暦に基いている。

ちなみに、2000年問題 – Wikipedia においても、

直接の原因は、プログラム内でを扱う際の年数の表現方法である。年数の表現をグレゴリオ暦の下二桁のみで行っている場合、2000年が内部で00年となり、これを1900年とみなしてしまい、例えば「データベースを日付順に並び替える処理をすると、順序が狂う」などの誤作動につながる可能性があるとされた。

また、現行の太陽暦であるグレゴリオ暦では

  1. 年が4で割り切れる年は閏年とする
  2. (1)のうち、年が100で割り切れる年は閏年としない
  3. (2)のうち、年が400で割り切れる年はこれを適用しない(つまり閏年とする)

というルールがあり、このため2000年は閏年だったが、誤って1と2のみを適用し、閏年としなかったプログラムが存在したため、この対応も併せて必要とされた。

 

他の言語における定義

Date - Rubyリファレンスマニュアル では、

new([year[, mon[, mday[, start]]]])

暦日付に相当する日付オブジェクトを生成します。

… 最後の引数は、グレゴリオ暦をつかい始めた日をあらわすユリウス日です。グレゴリオ暦の指定として真、ユリウス暦の指定として偽を与えることもできます。省略した場合は、Date::ITALY (1582年10月15日) になります。

Python の 6.10.3 date オブジェクト には、

日付は理想的なカレンダー、すなわち現在のグレゴリオ暦を過去と未来の両方向に無限に延長したもので表されます。 … この暦法は、全ての計算における基本カレンダーである、 … "予期的グレゴリオ (proleptic Gregorian)" 暦の定義に一致します。

Java の Calendar (Java Platform SE 6) における getInstance() メソッドにより返されるカレンダーオブジェクトを文字列表現にすると、

java.util.GregorianCalendar …

このクラスは GregorianCalendar (Java Platform SE 6) によると、

GregorianCalendar は、Calendar の具象サブクラスであり、世界のほとんどの地域で使用される標準的なカレンダシステムを提供します。 

GregorianCalendar は、グレゴリオ暦とユリウス暦をサポートするハイブリッドカレンダシステムで、単一の変わり目を処理します。

 

Data.Time の使用例

まずは Data.Time 関連のモジュールを読み込む。

Prelude> :m Data.Time
Prelude Data.Time>

Data/Time.hs を見てわかる通り、このモジュールで下位のモジュールがエクスポートされている。

 

日付の加算・減算

例えば、「2010年4月1日の 100 日後の日付」 を求めたい場合は、CalendaraddDays 関数を使い、

Prelude Data.Time> addDays 100 $ fromGregorian 2010 4 1
2010-07-10

「大晦日まで後何日か?」 求めたいなら diffDays 関数。

Prelude Data.Time> fromGregorian 2010 12 31 `diffDays` fromGregorian 2010 4 1
274

 

現在の日付・時刻は?

Ruby で現在の時刻を求める場合、

require 'date'
puts DateTime.now

同じように Haskell でも ClockgetCurrentTime 関数を使うと現在の時刻が表示される。

Prelude Data.Time> getCurrentTime
2010-11-08 03:09:48.769875 UTC

ただし、UTCTime 型の値が返される。この型の値は 協定世界時 – Wikipedia

協定世界時(きょうていせかいじ、UTC - Universal Time, Coordinated)とはセシウム原子時計が刻む国際原子時(TAI)をもとに、天文学的に決められる世界時(UT1)との差が1秒未満となるよう国際協定により人工的に維持されている世界共通の標準時である。…

世界各地の標準時はこれを基準として決めている。例えば、日本標準時は(JST)で協定世界時より9時間進んでおり、「+0900(JST)」のように表示する。

日本標準時を取得するには LocalTimegetZonedTime 関数を利用する。

Prelude Data.Time> getZonedTime
2010-11-08 12:10:21.06675 JST

 

LocalTime と ZonedTiem の関係

LocalTime 

A simple day and time aggregate, where the day is of the specified parameter, and the time is a TimeOfDay. Conversion of this (as local civil time) to UTC depends on the time zone.

LocalTime は日付と時刻を持つ型。これに TimeZone を加えると、世界標準の UTCTime になる。

タイムゾーンとは、

共通の標準時を使う地域全体を「等時帯」、「時間帯」または「タイムゾーン(time zone)」といい、その地域の標準時を示す際にはUTCとの差で示すことが多い。

(標準時 - Wikipedia)

例えば、日本は UTC + 9 で、この設定をする関数が hoursToTimeZone

localTimeToUTC 関数に TimeZone LocalTime 「2010年4月1日 6時0分0秒」 を与えて UTCTime を得る。

Prelude Data.Time> let tz = hoursToTimeZone 9
Prelude Data.Time> let lt = LocalTime (fromGregorian 2010 4 1) (TimeOfDay 6 0 0)
Prelude Data.Time> localTimeToUTC tz lt
2010-03-31 21:00:00 UTC

日本は UTC よりも 9 時間早いので、前日の 21 時となる。

 

現在からの日数を求める

先ほどと同様に、「現在の日付から 100 日後」 と、「現在から大晦日まで何日あるか」 を調べたい。

予め ZonedTime から日付と時刻に分解する補助的な関数を定義。

dayAndTime :: ZonedTime -> (Day, TimeOfDay)
dayAndTime zt = let lt = zonedTimeToLocalTime zt
                    day  = localDay lt
                    time = localTimeOfDay lt
                in (day, time)

day  = fst . dayAndTime
time = snd . dayAndTime

これを用いて、

main = do
-- 現在の時刻を取得 now <- getZonedTime -- 現在の日付から 100 日後は? print $ addDays 100 (day now) -- 現在から大晦日までは何日? print $ fromGregorian 2010 12 31 `diffDays` (day now)

 

後何分?

「現在の時刻から、特定の時刻まで後どれくらの時間があるか」 を調べたい。

時刻 t1 から t2 までの長さを求める diffTime を定義。

diffTime :: TimeOfDay -> TimeOfDay -> TimeOfDay
diffTime t1 t2 = timeToTimeOfDay 
                 (timeOfDayToTime t2 - timeOfDayToTime t1)

これを使い 「今から 22 時までの時間」 を求める例。

print $ diffTime (time now) (TimeOfDay 22 0 0) 

 

フォーマット

日付 `2010-04-01’ の表示を変更して `2010—04—01’ のような書式にしたい。

予め System.Locale をインポートしておき、defaultTimeLocale 関数を使えるようにしておく。

import System.Locale

formatTime 関数を使い、

print $ formatTime defaultTimeLocale "%Y--%m--%d" 
          $ fromGregorian 2010 4 1

 

全体のコード

関連記事

2010年8月30日月曜日

Java で型変数を利用して Cons , Nil クラスによるリスト表現 – map, filter, foldr, foldl の実装

Haskell でリストに対する関数を考えるときの視点」 のつづき

Haskell のリストに対応した Java クラス

前回は Haskell のリストに対応したクラスを定義し、リストの大きさを返すメソッド length を実装した。

08-29-20102

このクラスを使い、map, filter, foldr, foldl の実装を考える。

ここで予め Main クラスで利用するリストを作成しておく。以降この変数 list を使う。

IList<Integer> list = new Nil<Integer>().cons(4).cons(3).cons(2).cons(1);
System.out.println(list);    // 1 2 3 4 

 

累積変数を使った length メソッド

まずは、前回実装した length 関数を、末尾再帰で定義したときのような格好の実装を追加してみる。

Cons クラス...

    public int length(int acc) {
        return this.xs.length(acc + 1);
    }

累積変数 acc を導入し、そこへ要素の数を累積させる。

リストの末尾の Nil クラスでは、累積変数を返す。

    public int length(int acc) {
        return acc;
    }

Main クラスでこれを使ってみる。

        System.out.println(list.length(0));  // 4

 

map

次に、リストの要素に対して関数を適用する map 関数に相当するメソッドを定義する。

Haskell での map 関数の型は、

map :: (a -> b) -> [a] -> [b]

第1引数は、要素を別の型の値へ変換する関数。

Java では 「関数」 を関数の引数へ渡すことができないので、オブジェクトを代わりに渡し Strategy パターンで対応。オブジェクトの実装するインターフェイスを IUnaryOp として以下のように宣言し、利用するときはインターフェイスを実装した匿名クラスを作成する。

public interface IUnaryOp<A,B> {
    public B apply(A a);
}

apply 関数は、引数に A 型の値を与えると B 型の値が返ってくる。これは Haskell の map 関数の第1引数 (a –> b) に相当。

IList インターフェイスに map メソッドの宣言を追加。

    public <B> IList<B> map(IUnaryOp<A, B> f);

上記の型変数 B は、このインターフェイスを実装したクラスを生成するときではなく、このメソッドを使うときに決まるので、メソッドで型変数を宣言する。 ( cf. Java の型変数 とイレイジャ )

上記に伴ない Cons クラスで map メソッドを実装。

    public <B> IList<B> map(IUnaryOp<A, B> f) {
        return this.xs.map(f).cons(f.apply(this.x));
    }

Nil クラスにも map メソッドを実装。

    public <B> IList<B> map(IUnaryOp<A, B> f) {
        return new Nil<B>();
    }

これを Main クラスで使用する。

// map : 要素を 2 倍する
System.out.println(list.map(
        new IUnaryOp<Integer, Integer>() {
            public Integer apply(Integer a) {
                return a * 2;
            }
        }));     // 2 4 6 8

 

filter

filter 関数は述語を与え、リストから要素を抽出する関数。

Haskell の filter 関数の型は、

filter :: (a -> Bool) -> [a] -> [a]

第1引数は、ある型の値を渡すと Bool 型を返す述語

先ほどの map メソッドのように、filter 関数の第1引数に相当する述語に対応したメソッドを持つオブジェクトのインターフェイスを IPredicate とし、メソッドを宣言。

public interface IPredicate<A> {
    public boolean apply(A a);
}

apply メソッドは、引数に型 A の値を与えると boolean が返る。これは Haskell の filter 関数の第1引数 (a –> Bool) に相当する。

IList インターフェイスに filter メソッドの宣言を追加。

    public IList<A> filter(IPredicate<A> p);

上記に伴ない Cons クラスで filter メソッドを実装。

    public IList<A> filter(IPredicate<A> p) {
        return p.apply(this.x) == true
                ? this.xs.filter(p).cons(this.x)
                : this.xs.filter(p);
    }

Nil クラスにも filter メソッドを実装。

    public IList<A> filter(IPredicate<A> p) {
        return new Nil<A>();
    }

Main クラスでこれを使う。

// filter : 偶数を抽出
System.out.println(list.filter(new IPredicate<Integer>() {
    public boolean apply(Integer p) {
        return p % 2 == 1;
    }
}));     // 1 3

 

foldr

forldr はリストの右側から二項演算子により特定の型の値に畳み込む関数。

Haskell の foldr の型は、

foldr :: (a -> b -> b) -> b -> [a] -> b

これまでと同様、foldr の第1引数の関数に相当するメソッドを持つオブジェクトを生成するためにインターフェイス IBinaryOp1 を宣言。今回は二項演算子なので、引数が一つ増える。

public interface IBinaryOp1<A, B> {
    public B apply(A a, B b);
}

apply メソッドの引数には型 A の値と型 B の値を与えると型 B の値が返る。これは Haskell の foldr 関数の第1引数の型 (a –> b –> b) に相当する。

IList インターフェイスに foldr メソッドの宣言を追加。このとき Haskell の foldr の型と比較しながら宣言を考えるとよい。

    public <B> B foldr(IBinaryOp1<A, B> f, B z);

上記に伴ない Cons クラスに foldr メソッドを実装。

    public <B> B foldr(IBinaryOp1<A, B> f, B z) {
        return f.apply(this.x, this.xs.foldr(f, z));
    }

Nil クラスにも foldr メソッドを実装。

    public <B> B foldr(IBinaryOp1<A, B> f, B z) {
        return z;
    }

これを Main クラスで使う。

// foldr : 要素を足し合わせる
System.out.println(list.foldr(new IBinaryOp1<Integer, Integer>() {
    public Integer apply(Integer a, Integer b) {
        return a + b;
    }
}, 0));                 // 10

// foldr : リストのコピー
System.out.println(list.foldr(
        new IBinaryOp1<Integer, IList>() {
            public IList apply(Integer a, IList b) {
                return b.cons(a);
            }
        },
        new Nil()));    // 1 2 3 4

 

foldl

foldr が右から畳み込むのに対して、foldl は左から畳み込む関数。

Haskell の foldl 関数の型は、

foldl :: (a -> b -> a) -> a -> [b] –> a

よく見ると foldr と型が少し違うのがわかる。

foldr :: (a -> b -> b) -> b -> [a] -> b

foldr のときと同様に、第1引数の関数に相当するメソッドを持つオブジェクトのインターフェイスを IBinaryOp2 とする。

public interface IBinaryOp2<A, B> {
    public A apply(A a, B b);
}

apply メソッドは引数型 A の値と型 B の値を与えると、型 A の値が返る。これは Haskell の foldl の第1引数 (a –> b –> a) に相当する。

IList インターフェイスに foldr メソッドを追加。ただし、foldr のときのように Haskell の型と完全に同じようになっていないことに注意。理由は、Cons クラスで実装したところを見るとわかる。

    public <B> B foldl(IBinaryOp2<B, A> f, B z);

上記に伴ない Cons クラスに foldr メソッドを実装。

    public <B> B foldl(IBinaryOp2<B, A> f, B z) {
        return this.xs.foldl(f, f.apply(z, this.x));
    }

特に第1引数が

IBinaryOp2<A, B>

ではなく、

IBinaryOp2<B, A>

であることに気をつける。

後者の場合、IBinaryOp2 インターフェイスに照らし合わせると、メソッド apply の型は次のようになる。

public B apply(B b, A a)

このように型変数名が変わっても本質的な意味は同じ。

しかし、これを Cons クラスのメソッドのシグニチャに用いると意味がそれぞれ異なる。理由は、Cons クラスが持つ型変数が A であり、この A と上記 apply メソッドの型変数名 A が一致するので、これによりメソッド全体において型変数 A の意味が定まるから。 (多分… ^^; 色々と試して動いたのがこれだったので、後付けで理由を考えた。)

Nil クラスにも foldr メソッドを定義。

    public <B> B foldl(IBinaryOp2<B, A> f, B z) {
        return z;
    }

これを使ってみる。

// foldl : 要素を足し合わせる
System.out.println(list.foldl(
        new IBinaryOp2<Integer, Integer>() {
            public Integer apply(Integer a, Integer b) {
                return a + b;
            }
        },
        0));       // 10

// foldl : 要素を掛け合せる
System.out.println(list.foldl(
        new IBinaryOp2<Integer, Integer>() {
            public Integer apply(Integer a, Integer b) {
                return a * b;
            }
        },
        1));       // 2

 

メソッドチェーン

最後に上記 map, filter, foldr でメソッドチェーン。

// メソッドチェーン : filter => map => foldr :
System.out.println(list.filter(new IPredicate<Integer>() {
    public boolean apply(Integer p) {
        return p % 2 == 0;     // 偶数を抽出
    }
}).map(new IUnaryOp<Integer, Integer>() {
    public Integer apply(Integer a) {
        return a * 3;          // 3 をかける
    }
}).foldr(new IBinaryOp1<Integer, Integer>() {
    public Integer apply(Integer a, Integer b) {
        return a * b;          // 掛け合わせる
    }
}, 1));     // 72

結論は、こんなゴチャゴチャしたコードは書きたくないということで。(+_+)

型を書くのが面倒なので型推論してくれぇ~。

てゆうか、これからの実用所はやっぱ Scala っすか ?

 

ここまでのコード

クラス図。(メソッドにおける型変数を無理矢理書いてしまったけれど。)

08-29-20103

 

関連記事

Haskell でリストに対する関数を考えるときの視点 - オブジェクト指向からの類推

Haskell のリストに対する関数の定義

データコンストラクタによる値の生成

Haskell ではリストが特別扱いされている。(3.7 リスト)

リストを生成したいときは、

[0,1,2,3]

のように書く。

他の代数的データ型と違い、組込みの構文を使って表現できるため、最初リストにデータコンストラクタがあることに気がつかなかった。

データコンストラクタを使って書くなら、

0:1:2:3:[]

しかし、これまた随分長いこと

(:)

がデータコンストラクタであるという認識なし。二項演算子 (:) は、要素とリストをくっつけるために標準で用意されている一般的な関数だと思っていた。 ^^; (cf. Haskell の cons (コンス) )

更に言えば、

[]

もデータコンストラクタだと思わず。なんだかよくわからないけれど、要素のないリストを表わすのに [] を使うと覚えていただけ。

大学で情報系を専攻していたら、Lisp や Scheme などの所謂王道 (?) を学んだろうから、リストを生成するための cons は馴染深く、上記のような疑問を抱かなかったはず。いかんせん心理学だったもので、 Lisp は認知系の教科書の片隅にあった AI に関したコラムで見たのみ。その頃、括弧だらけの変態プログラミング言語だと思っていた。

 

関数を定義するときの場合分けはなぜ?

ところで、リストに関する関数の定義を見ると、

  • 空の場合
  • 要素がある場合

に場合分けるすることが多い。

例えば、リストの長さを返す関数を定義したいなら、

length []     = 0
length (x:xs) = 1 + length xs

「空リスト」 と 「空ではないリスト」 の場合に定義が分けられている。

リストの先頭要素を返す Prelude の head 関数 を見ても同じ。

head                    :: [a] -> a
head (x:_)              =  x
head []                 =  badHead

badHead :: a
badHead = errorEmptyList "head"

「空リスト」 に head を適用するとエラーを返すだけなのだけれど、定義がキチンとされている。

もちろん、定義してなくても動作しないことはない。その場合、

Non-exhaustive patterns in function head

というように、パターンマッチで失敗したことが通知される。

しかし、実装では空リストに対して head を適用すると、以下のエラーが返される。

*** Exception: Prelude.head: empty list

length 関数の定義に話を戻す。

はじめてこの定義を見たとき、どうも腑に落ちなかった。直感的に理解できないと言うか、定義が十分であることを肌で感じ取れないというか…。 (+_+)

命令型の言語にどっぷり浸っていたので、for 文のような繰り返しのための制御文で大きさを数えられないのかな?と最初思ったり。

 

宣言的という意味は?

関数型言語の説明としてよく聞くのが、

… 処理方法ではなく対象の性質などを宣言することでプログラミングする…

( 宣言型プログラミング - Wikipedia より )

A program that describes what computation should be performed and not how to compute it

( Declarative programming - Wikipedia より、太字は引用者による )

というもの。これが何を意味しているのかはさて置き、額面通り素直に受け取るなら、lenght の空リストに対する定義は、

length []     = 0

次のように解釈できる。

空リストの 「大きさ」 という性質は 0

これに対し、「空ではないリスト」 の定義はどう解釈したら性質を宣言していると言えるのだろう?

length (x:xs) = 1 + length xs

例えば、

リストの大きさは、先頭要素以外のリストの「大きさ」に 1 足した値

一見わかりずらい日本語。

ここで最初に気になったのは、パターンマッチでリストを 「先頭」 と 「残りの要素」 に分けて考えている処理。何となく what というよりは how を意識しているような気がするので、果たしてこれが性質を表わしていると言えるのか。…てゆうか、そもそも何をもって what による定義であると見なせるのかわからない。

また、再帰的な定義は、性質 (what) を記述しているのか、それとも処理方法 (how) を示しているのかどうかも判断できず。 (@_@;

ここまでをまとめると、以下の 3 点が不明。

  • リストに対する関数で場合分けをすること
  • how ではなく what により計算を表現すること
  • 再帰的な定義が上記 2 点から見て何を意味するか

 

Control flow

まずは、宣言的なプログラミングについてもう少し調べる。

Declarative programming - Wikipedia によると、

declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow.[1]

上記 Control flow - Wikipedia とは、

control flow (or alternatively, flow of control) refers to the order in which the individual statements, instructions, or function calls of an imperative or a declarative program are executed or evaluated.

宣言的なプログラミングとは、「あれやってから、これやって…」 というように順序を記述することなく、計算のロジックを表現する方法だと。確かに例として挙げられている SQL の SELECT は、順序を指定することなく欲しいデータを取得できる。

Control flow に関しては、Neil Mitchell's Haskell Blog: Functional Flow Control の説明もわかりやすい。

In normal programming languages, there are many keywords for flow control, such as for, while etc. These flow control keywords encode a particular pattern of iteration, such as looping over a range (in the case of for) or continuing until some condition holds (while). Imperative programming languages continue to add more iteration keywords: both C#/Java have introduced some form of for-each; Python/C# have yield; Ada has many variants.

( 太字は引用者による。)

for や while などの control flow を表わすキーワードは、特別な計算のパターンを符号化していると。

計算のパターンに関して、CiNii 論文 -  関数プログラミングの実際 (<特集>関数型プログラミングと計算の基礎) では次のように述べられている。

構造的プログラミングは... 洗練された制御構造とデータ構造を用いてプログラムを設定・作成すべきであるという教えであり...
手続き型言語の文の構造化機構 ( if e then c1 else c2 や while e do c など ) とデータ構造化機構 ( array[T1] of T2 や record...end など) によって、文やデータの構成要素をより大きな構成単位に組み上げるという機能を利用している。つまり、これらは構成要素を結びつける糊(glue)の役割を果たしている...

関数プログラミングにおいては、関数を組み合わせる関数がプログラムの構成要素を結合する糊になる...

手続き型の糊の弱さのひとつに、そこでは構成要素を結合する際の計算のパターン、たとえば if e then c1 else c2 が制御の流れしか捉えていないということがある。

関数プログラミングの考え方は計算のパターンを抽象化して捉え、そこで成り立つ法則を用いてプログラムを合成したり、その性質を調べたりすることが特徴的である。... 計算パターンの性質は個々の構成要素とは独立しており、それ自体としても関数としての存在意義をもっている。...
関数 map のように、計算パターンを関数によって抽象することができる...
内包表記を map とほかのいくつかの関数を用いた式に変換することもできる...
左右のそれぞれの「たたみ込み(fold)」を行う計算パターンも一般的である。...
計算パターンを表わす関数の形式的な定義は再帰的に行う...

つまり、関数プログラミングでは、制御の流れも関数により表現し、そういった計算のパターンは再帰的に定義することができると。

先ほど 「リストの大きさを知りたいとき、for ループで数え上げればいいじゃん」 と反射的に思ったのは、「制御の流れを示す flow control を使う」という命令型の考え方であると言える。

ついでなので、length 関数を foldr や map などの計算のパターンを表現した関数を使って定義するなら、

length = foldr (\_ acc -> acc+1)  0

length = sum . map (\_ -> 1) 

数え上げるという意味では、末尾再帰により特定の変数にカウンタの役割を持たせた方が自然なのかも。

length xs = length' xs 0
    where
      length' [] acc     = acc
      length' (x:xs) acc = length' xs (acc+1)

話を元に戻して、上記から考えると、

length (x:xs) = 1 + length xs

の定義は、「あの計算をしてから、この計算をする」 ということが明示的に述べらていないことから宣言的な定義であると言えそうな気もするし、イヤイヤ、再帰的な定義そのものが実際には制御の流れを統制している計算のパターンだから、この部分が計算の how を表現しているとも言えそうな気がする。

それにしても、このままwhat と how について考えても length 関数の直感的な理解へと進みそうにない。また、制御の流れを意識しない定義は、定義としては満足かもしれないけれど、どうしてもイメージとして府に落ちないし、しっくり来ない。これは命令型脳であることの証左なのか。。

ともあれ、先ほど挙げた疑問の内、what / how について考えるのは諦め、以下の 2 点について直感的なイメージができるようにしたい。

  • リストに対する関数の場合分けをする意味
  • how ではなく what により計算を表現すること
  • 再帰的な定義が何を意味するか

 

Java で cons 相当のクラスを定義して Haskell のリストを再考

自分の場合、直感的な理解ができたと感じるのは、オブジェクト指向からの類推で何が何に相当するのか対応付けができたとき。もう少し正確に言うなら、計算を担当する小人さんを想定し、小人たちのコラボレーションをイメージできた場合。 独立したモジュールを想定し、影響を与える範囲をほどほどに限定して、各々に役割を与え詳細を隠蔽。 「あれして、これして」 と各々に頼み、自分が知っている情報を用いて計算を行ってもらうというモデルは素朴でわかりやすい。

 

Cons クラスを元にリストを定義

では、Java で Haskell のリストのような構造を定義する場合、どのように考えればいいのだろう?Haskell のリストは特別に組込まれているが、代数的データ型で定義できるとしたら以下の通り。

data  [a]  =  [] | a : [a]  deriving (Eq, Ord)

( 6.1.3 リスト より )

2 つのデータコンストラクタからリスト型の値が作られる。

  1. 空リスト
  2. 型 a である要素 と リスト型をフィールドに持つ値

A Gentle Introduction to Haskell: Classes によると、

Haskell のクラスは大筋では Java のインタフェースに類似している。 Java のインタフェース宣言とおなじように、Haskell のクラス宣言はオブジェクトそのものを定義するのではなく、オブジェクトを使う手順を定義 している。

よって Java で定義するなら、リスト型をインターフェイスと見なし、それを実装したクラスが二つ。

  1. 空リストに対応したクラス : Nil
  2. 先頭要素 と 残りの要素に対する参照をフィールドとして持つクラス : Cons

全体として Composite パターン。

 img02-16-2010[1]

リストの大きさを返す length メソッドを実装するなら、IList インターフェイスの定義は、

public interface IList {

    public int length();
}

空リストに相当するクラス Nil は、

public class Nil implements IList {

    /**
     * リストの要素数を返す
     * @return 空リストなので 0
     */
    public int length() {
        return 0;
    }

    @Override
    public String toString() {
        return "Nil";
    }
}

(:) に相当する Cons クラスは、

public class Cons<T> implements IList {

    private T a;        // 先頭要素
    private IList l;    // 残りの要素

    public Cons(T a, IList l) {
        this.a = a;
        this.l = l;
    }

    /**
     * このリストの先頭に要素を追加する
     * @param a 先頭に追加する要素
     * @return 先頭に要素を追加したリスト
     */
    public Cons cons(T a) {
        return new Cons<T>(a, this);
    }

    /**
     * このリストの要素数を返す
     * @return リストの要素数
     */
    public int length() {
        return 1 + this.l.length();
    }

    @Override
    public String toString() {
        return this.a.toString() + "," + this.l.toString();
    }
}

試してみる。

public class Main {

    public static void main(String[] args) {
        Cons<Integer> l = new Cons<Integer>(3, new Nil()).cons(2).cons(1);
        System.out.println(l);
        System.out.println(l.length());
    }
}

結果は、

1,2,3,Nil
3

 

他の実装方法

下図のように Node クラスのオブジェクトを一方向につなぎ、次の Node オブジェクトを指し示していないオブジェクトを末尾と見なすという実装も考えられる。(cf. gist: 307278 - GitHub)

img02-20-2010[2]

 

Haskell の代数的データ型の定義と Java のクラス定義を対比して

しかし、Haskell のリストをオブジェクト指向から類推するとき、むしろ先ほどのように型をインターフェイスと見なし、各々のデータコンストラクタをクラスと対応させる方がしっくり来る。なぜなら、

同じ型だけれど、それぞれが違うもの

ということが印象付けられ、各々の型がどう振る舞うべきかを考えるよう自然に動機付けられるから。つまり、IList インターフェイスにメソッドの宣言を追加したら、それを実装する Nil クラスと Cons クラスにメソッドを実装しなくてはならないことをコンパイラによって嫌でも意識させられる。

img02-16-2010[1] (3)

同じように Haskell で [a] 型の値に対する関数を定義するとき、上記に基いて考えると、データコンストラクタ [] と (:) によって生成される値に対する関数を定義する必要があることに思いが至る。

img02-21-2010[1]

ただし、Haskell の場合、以下のように関数の型を宣言しても、必ずしも [] と (:) に対して定義しなければコンパイル時にエラーが出るわけではないことに注意。

length :: [a] -> Integer
length []     = 0
-- length (x:xs) = 1 + length xs

 

オブジェクト指向で実装するときのイメージ

ところで、先ほどの Haskell と Java の length の実装を比較するとよく似ている。

length []     = 0
length (x:xs) = 1 + length xs

Java の Nil クラス …

    public int length() {
        return 0;
    }

Cons クラス …

    public int length() {
        return 1 + this.l.length();
    }

Java で実装したときの頭の中のイメージは下図の通り。

img02-16-2010[1][5]

  • Nil クラスは要素を持たない。 よって、length の問い合わせで 0 を返す。
  • Cons クラスは、
    • 自分が直接持っている要素は 1 つ。
    • リストに対する参照を持っており、これに対し length の問い合わせができる。その結果に自分が持っている要素の数を 1 つ足して、最終的な lenght の問い合わせに答える。

08-26-20101

この実装方法は、極めて自然に考えることができる。特に、再帰的な関数の呼び出しが、連鎖しているオブジェクトの連続的な呼び出しとして表現されるので、動作しているイメージをしやすい。

 

Haskell をオブジェクト指向的に見ると…

この見方をしたとき、Haskell のデータコンストラクタによるパターンマッチの動作と、再帰的な定義の意味が違って見えるようになった。

ここでリストの値がデータコンストラクによって生成されることを明確に意識するために、リストを代数的データ型で定義する。

data List a = Nil 
            | Cons a (List a) 
              deriving Show

これで Java で定義したインターフェイスとクラスに対応させて考えやすくなる。

List a 型に対して、リストの大きさを返す関数を定義。

length             :: List a -> Int
length Nil         = 0
length (Cons x xs) = 1 + length xs

これに対して次のような見方をする。

img02-18-2010[3]

  1. パターンマッチにおけるデータコンストラクタ Cons をクラスと見なす。
  2. データコンストラクタにより分解されたフィールドの値は、Cons クラスのプライベート変数。
  3. x は Cons クラスが直接持っている値で、xs は List a 型の値への参照。

length 関数を Cons クラスのメソッドと見なし、右辺は Cons クラスのフィールドにアクセスできると考える。関数の適用する対象をオブジェクトと見なすなら、

length (Cons x xs)   =>   (Cons x xs).length()

length xs   =>   xs.length()

のように見立てると対応付けやすい。

Java で型変数を利用して Cons , Nil クラスによるリスト表現 – map, filter, foldr, foldl の実装 」 につづく

 

関連記事

2010年2月27日土曜日

Java の型変数 とイレイジャ - 具体的な型の指定を忘れずに

Java の型変数に慣れるための練習。

例えば、変数を 『箱』 に見たて、「箱の中の値」 と 「別の何らかの値」 を引数として与えると、二つをくっつけて返す takeOut メソッド があるとする。

takeOut (箱の中の値, 別の何らかの値)  =>  2 つをくっつけた値

ただし、この関数は次の制約を満さなければならないという条件付きで。

takeOut メソッドが返す値の型は、箱の中身の型ではなく、もう一方の「別の何らかの値」の型と一致する。

箱を int 型として、もう一方の値を int 型としたときのイメージは以下の通り。

img02-26-2010[1]

同じく箱を int 型として、もう一つの値を String 型としたときのイメージ。

img02-26-2010[2]

実装。

public class Main00 {

    public static void main(String[] args) {
        int box = 100;
        System.out.println(box);                 // 100
        System.out.println(takeOut(box, 200));   // 300
        System.out.println(takeOut(box, "円"));  // 100円
    }

    static int takeOut(int box, int val) {
        return box + val;
    }

    static String takeOut(int box, String str) {
        return box + str;
    }
}

 

特定の型に依存している箇所

上記の実装は特定の型に依存している。型に依存している部分は次の 3 つ。

  • 箱の中身  … (A)
  • 別の何らかの値 (takeOut メソッドの第2引数)  … (B)
  • 上記 (A), (B) をくっつける演算子(関数)

これらが可変になるようにしたい。

 

クラスで型変数を宣言

まずは、箱をクラスとして実装し、中身を保持するための型変数を導入。型変数により箱はどんな型でも受け入れることができるようになり、入れた中身の型に応じたメソッドを作成可能。

「箱に入れた中身を取り出す」だけの takeOut メソッドから実装する。

img02-26-2010[3]

クラスで型変数を宣言し、その型変数を「箱の中身」を保持するためのプライベート変数の型として宣言し、takeOut メソッドはその型変数を返すようにする。中身は箱を生成するときに渡すとするなら、

public class Box<A> {

    private A contents;

    Box(A contents) {
        this.contents = contents;
    }

    A takeOut() {
        return this.contents;
    }
}

 

メソッドで型変数を宣言

先ほどの takeOut メソッドを見ると、「箱の中身」と「別の何らかの値」をくっつける関数は 2 項演算子 `+’ だった。これを、与えられる値の型に応じて、適切な 2 項演算子を適用できるように変更したい。ただし、先ほどの制約は守ることにする。

takeOut メソッドが返す値の型は、箱の中身の型ではなく、もう一方の「別の何らかの値」の型と一致する。

これをイメージしたのが下図。「箱の中身」を型変数 A で、「もう一つの値」に対応したものを型変数 B とし、二項演算子を f で表現した。

img02-26-2010[4]

ところで、Java は関数を直接メソッドに渡せないので、Strategy パターン でオブジェクトを渡す必要がある。このとき、2項演算子を適用するメソッドを apply とし、インターフェイスを IBinaryOp とする。 apply メソッドの第1引数は「箱の中身」、第2引数は「別のもう一つの値」が渡されるとするなら、

public interface IBinaryOp<A, B> {
    public B apply(A a, B b);
}

第2引数と返り値は、型変数 B で一致させることにより型チェックができる。

これで Box クラスの takeOut メソッドを次のように定義できる。

<B> B takeOut(IBinaryOp<A, B> f, B b) {
 return f.apply(this.contents, b);
}

型変数 B の具体的な型は、Box クラスの生成時に決まるのではなく、このメソッドを使うときに決まる。よって、メソッドにおいて型変数を宣言。もし、これをクラスの型変数としてしまうと、Box の生成時に型を指定しなければならず、また、そのときに指定した型に固定されてしまう。

ここまでのコード。

 

型変数に具体的な型を指定して使う

Box クラスを作成できたので、Main クラスの main メソッドで使ってみる。はじめは箱に入れた値を取り出すだけ。

Box<Integer> box1 = new Box<Integer>(100);
System.out.println(box1.takeOut());            // 100

Box クラスをインスタンス化する文において、型宣言と new の双方において具体的な型 <Integer> を指定することを忘れずに。

 

匿名クラスでインターフェイスを実装したクラスを作成

次に、「箱から取り出した値」と「別のもう一つの値」を引数に取るメソッドを作成する。これは匿名クラスを利用した。

System.out.println(box1.takeOut(
        new IBinaryOp<Integer, Integer>() {
            public Integer apply(Integer a, Integer b) {
                return a + b;
            }
        },
        200));                                 // 300

先ほどと同じく、IBinaryOp インターフェイスを実装するクラスをインスタンス化するときに具体的な型の指定を忘れずに。これによりコンパイル時に型チェックがされる。

ところで、takeOut 関数の引数と返り値の型は以下の通りだった。

<B> B takeOut(IBinaryOp<A, B> f, B b) {

上例の場合、型変数 B に Integer 型が対応する。よって、takeOut の第2引数の値を文字列にすると、「型が違うよ」とコンパイラが間違いを指摘してくれる。具体的な型を指定してないと型チェックはされず、メソッドでキャストをしなければならない。

もし、 takeOut の第2引数の値を文字列にしたいなら、この定義では以下のように IBinaryOp インターフェイスを実装したクラスのオブジェクトを生成するときに具体的な型として String を指定。

System.out.println(box1.takeOut(
        new IBinaryOp<Integer, String>() {
            public String apply(Integer a, String b) {
                return a + b;
            }
        },
        "円"));                                // 100円

 

型変数のメリット

当然ながら、Box クラスで型変数を利用しているので、箱の中身の型が違っていても問題ない。main メソッドにおいて、中身を文字列に変更して使うなら、

Box<String> box2 = new Box<String>("百");
System.out.println(box2.takeOut());              // 百

System.out.println(box2.takeOut(
        new IBinaryOp<String, String>() {
            public String apply(String a, String b) {
                return a + b;
            }
        },
        "円"));                                  // 百円

 

NetBeans でメソッドの引数と返り値の型を補完

ちなみに、Netbeans を使っている場合、IBinaryOp インターフェイスを実装するときに、

new IBinaryOp<Integer, Integer>()

を入力した後、

img02-27-2010[1]

メニューより「ソース > コードを修正...」を選択するか、コードの左に出るランプのようなアイコンをクリックすると、「すべての抽象メソッドを実装」がポップアップし、

img02-27-2010[2]

選択すると型に応じた空のメソッドが生成される。

img02-27-2010[3]

 

コード全体

ここまでのコード。

 

型のイレイジャに注意

ところで、型変数を利用したクラスを使うのは、特定の型に依存した実装ではなく、色々な型に対応でき、かつ、コンパイル時に型のチェックができるからだった。ただし、型変数があるにも関わらず、型を指定せずにコードを書くことも可能。これは、Java総称型メモ(Hishidama's Java Generics Memo) によると、

総称型として定義されているクラスから型パラメータを除去したクラスを「型のイレイジャ(型消去:type erasure)」と呼ぶ。…

つまり「List<T>」に対し「List」、「Map<K, V>」に対し「Map」が“イレイジャ”。

例えば、先ほどの例で Box クラスのオブジェクトを生成するとき、型宣言で具体的な型を指定しなかったとする。

Box box1 = new Box<Integer>(100);

Box クラスの takeOut メソッドは IBinaryOp の 型変数 B が決まると、第2引数と返り値の型が決まるのだった。

<B> B takeOut(IBinaryOp<A, B> f, B b) {

しかし、この場合 box1 の型変数が指定されてないためなのか(?)、コンパイラが型チェックをしてくれない。(イレイジャは関係している型変数のチェックを無効にしてしまうのかな?)

System.out.println(box1.takeOut(
        new IBinaryOp<Integer, Integer>() {
            public Integer apply(Integer a, Integer b) {
                return a + b;
            }
        },
        "hoge"));   

上記は実行した段階で「キャストできません」と例外が投げられる。

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

具体的な型の指定を忘れるとコンパイル時にチェックされない。ジェネリクスの恩恵を受けるために忘れないように。

 

Haskell の場合

同じ意味合いのコードをHaskell で実装するなら、

data Box a = Box a deriving Show

takeOut         :: Box a -> a
takeOut (Box x) = x

takeOutWith             :: (a -> b -> b) -> b -> Box a -> b
takeOutWith f z (Box x) = x `f` z

main = do let box1 = Box 100
              box2 = Box "Hyaku"
          print $ takeOut box1                                -- 100
          print $ takeOutWith (+) 200 box1                    -- 300
          print $ takeOutWith (\a b -> show a ++ b) "En" box1 -- "100En"
          print $ takeOutWith ((++).show) "En" box1           -- "100En"

          print $ takeOut box2      -- "Hyaku"
          print $ takeOutWith (++) "En" box2     -- "HyakuEn"

う~ん、シンプル。

Java の方は本当にあれでいいのかなぁ。。 (+_+) 実はここで書いた例は、Java で Cons クラスをベースにリストを作り、その上で foldr, foldl に相当するメソッドを実装したらどうなるか試していたとき、型チェックがされなくておかしなぁ~と思い、よりシンプルな例にして試したもの。上記の不自然に見える制約は、foldr, foldl に与える 2項演算子における型と同じようにしたかったので、そのようにした。

 

関連記事

参考サイト

2010年1月14日木曜日

被演算子の一つを返す AND, OR - デフォルト値の設定

if と真偽値 の続き。

 

Python のブール演算における例外的なこと

前回見たように、Python では if 文においてたくさんの値が `偽’ と見なされる。ただし、 ドキュメント の最後には次のように書かれている。

ブール値の結果を返す演算および組み込み関数は、特に注釈のない限り常に偽値として 0 またはFalse を返し、真値として 1 または True を返します (重要な例外: ブール演算 "or" および "and" は常に被演算子の中の一つを返します)。

(2.3.1 真値テスト より、太字は引用者による。もしくは 6. Built-in Types — Python v2.6.4 documentation を参照)

例えば、組み込み関数である all, any なら、

l = range(10,100)
print all(map(lambda x: x >= 10, l)) #=> True
print any(map(lambda x: x < 10, l))  #=> False

というようにブール値を返す。では、「重要な例外」と書かれている or, and は、どういう意味で重要なのだろう?なぜ素直にブール値を返さないのか?

ちなみにこれが Java なら、

条件AND式の型は,常に boolean とする。…

条件OR式の型は,常に boolean とする。

(Java言語規定 式 より)

のように必ずブール値を返す。

 

if 文における and, or

偽 と解釈される値の場合

普通、and 演算子は次のように if  と組み合わせて使用される。

x = "hoge"; y = "piyo"

if x != "" and y != "":
    print x, y

上記を実行すると、”hoge piyo” と出力。

ところで、Python の文字列は、if の検査において、空文字 でそれ以外は 真 として扱われた。よって、先ほどの if による空文字かどうかの検査の部分、

if x != "" and y != "":

を次のように書換えることができる。

if x and y:

この一見すると奇妙で意味を汲みにくい書き方を素直に読むなら、

もし x かつ y ならば…

となり、最初に書いた

もし x が空文字であり、かつ、 y も空文字であるならば…

に比べると曖昧に見える。

 

評価のされ方を確認

上記のように の値であることを想定して書いたときの、if 文と and, or 演算子における値の評価を確認してみることに。最初はちょっと奇妙に思えるが、以下のように文字列に and , or 演算子を適用。

print "hoge" and "piyo" and "fuga"  #=> “fuga”
print "hoge" or  "piyo" or  "fuga"  #=> “hoge”

最初見たドキュメントの通り、被演算子の中の一つが返されているのがわかる。このルールは、5.10 ブール演算 (boolean operation) によると、

x and y は、まず x を評価します; x が偽なら、x の値を返します; それ以外の場合には、 y の値を評価し、その結果を返します。

x or y は、まず x を評価します; x が真なら、x の値を返します; それ以外の場合には、 y の値を評価し、その結果を返します。

(andnot も、返す値を 01 に制限するのではなく、最後に評価した引数の値を返すので注意してください。

(太字は引用者による)

これに従うと、

if x and y:

の例では、次のような評価の流れとなる。

  1. and 演算の評価
    • x は空文字でないので真
    • y は空文字でないので真
    • y の値を返す
  2. if の評価
    • and 演算子の結果は空文字でないので真

 

JavaScript で || によってデフォルト値を設定する方法

4873113911ところで、Python に負けず劣らず と解釈される値が多い JavaScript では、Python の or に相当するのは ||JavaScript: The Good Parts (pp.24-25) には、この || 演算子を使った「デフォルト値」を設定する方法が書かれている。

例えば、`名前と年齢’ を対にしたハッシュを定義し、ハッシュの要素ではないものを参照しようとしたとする。

var persons = {"Tarou" : 10, "Jirou" : 20, "Hanako" : 30};
var age = persons["Saburou"] || 0;

|| 0 と書くことにより、 age には 0 が代入される。先ほどの Python の or と同じルールを思い出しながら評価の流れを考えると、

  1. persons[“Saburou”] に相当するものがないので undefined が返される。
  2. undefined は 偽 と見なされる。
  3. || 演算子のルールに従い、|| の右辺の 0 が評価され、0 が返される。

 

Python でも or でデフォルト値を設定

Python でも同じようにして、デフォルト値を設定できる。

a = 0
print a or 100 #=> 100

ん~ (@_@; 一見意味がわかりずらい。。

このようにデフォルト値の設定を or 演算子でできるのは、

  1. 偽 と見なされる値がいくつかあり、
  2. or の結果がブール値ではなく評価した値そのものを返している

ことによる。

 

ところで、どうしても短く書きたいなら、条件式 を使った方が意図が明確になる。
print a if a else 100

いや、正直言うとこの書き方も好きではない。

print a if a != 0 else 100

頭が堅いのかな?こうでないと今一安心できない ^^;

 

Ruby でも || でデフォルト値を設定

Ruby の場合は、nil が 偽 と見なされるので、次のように書ける。

a = nil
p a || 100 #=> 100

しかし、Python や JavaScript の癖が抜けずに、

a = 0
p a || 100 #=> 0

と書くと、意図したものと違う結果になるので注意。

 

ある一つの機能は、一つのことだけのために限ってほしい

結局、言語によって 偽 と見なす値が異なることと、and , or のような演算子がブール値でなく、評価した値のいずれかを返すという仕様が、自分のようなすぐに忘れる脳みそにとってはどうしても好きになれない。 (+_+) そもそもブール演算に用いるものを、デフォルト値の設定に使うという発想が自分はダメ。道具は用途を限り、それにのみ使えるようにしておかないと、脳みその容量が少ないので混乱してしまう。 一時慣れたとしても、しばらく使ってないと忘れる自信ならたっぷりある。(o_ _)o~†

加えて、演算子が場合に応じて異なる型の値を返すということも居心地が悪い。 (+_+)  「返すならブール型だけで勘弁してください」と言いたい気分。異なる値を返すなら、特定のインターフェイスを実装している型、もしくは、Haskell で言うなら同じ型クラスのインスタンスのみに限定してほしい。以前、少しの手間を惜しみ、ある関数で異る型の値を返すようにしたことがある。一時的の`つもり’で書いたのだけれど、それを忘れ、後になって見つけづらいバグになってしまった。 もちろん、「ちゃんとテストコード書いておけよ」という話なんだけれど。

とにかく、ポカミスを連発し、ミスをしてないという思い込み、エラーの発見が苦手な自分のようなタイプにとって、例外的なことは本能的に避けたくなる。多分、そうでない人にとっては、便利さとのトレードオフが考慮された例外的な事柄は便利だと感じるのだろうなぁ。

 

追記 (2010.1.16) : Google Python スタイルガイド では、True/False の評価について次のように書かれている。

可能な場合は、非明示的な false を利用する。…

利点:
Python のブール値を使うと、可読性が高まり、エラーを防ぐことができます。さらにほとんどの場合で、これは高速です。

うーん、可読性高まるのかなぁ~ (@_@; 

 

Haskell で似た関数を定義する

Haskell で同じようにデフォルト値を設定しようと、次のように書くと、

a = 0
b = a || 100

|| が適用する型が一致しないので、当然エラーとなる。どうしても上記のようなデフォルト値を設定する演算子が使いたいのなら、|| とは別に定義する。

デフォルト値を設定する演算子を ||| で定義するなら、

(|||) x d = if x == 0 then d else x

となる。これを使って、

a = 0
b = a ||| 100  -- b は 100

ただし、これでは数値にしか使えない。

 

JavaScript の or に相当する ||| 関数

前回、Haskell で Python のように型ごとに if の解釈を変えるためにオーバーロード を利用した。コードは以下の通り。

class Boolean b where
    isTrue :: b -> Bool

instance Boolean Bool where
    isTrue True  = True
    isTrue False = False

instance Boolean Integer where
    isTrue 0 = False
    isTrue _ = True

instance Boolean [a] where
    isTrue [] = False
    isTrue _  = True

if' x e1 e2 = if isTrue x then e1 else e2

それぞれの型でオーバーロードした if’ 関数は 型クラス Boolean のインスタンスに適用できる。これを元に if’ を定義したときと同じように考える。

(|||) e1 e2 = if isTrue e1 then e1 else e2

… あれ? ||| と if’ の定義は形がよく似ている。こんな冗長なコードを書いていたらだめか。。

if’ を使って書き直すと、

(|||) e1 e2 = if' e1 e1 e2

引数を省略して以下のように書けるけれど、これだと意図がわかりずらいかな。 (@_@;

(|||) e1 = if' e1 e1

試してみる。

*Main> 0 ||| 100
100
*Main> 1 ||| 100
1
*Main> [] ||| [1,2,3]
[1,2,3]
*Main> [1,2] ||| [1,2,3]
[1,2]
*Main> "" ||| "hoge"
"hoge"
*Main> "piyo" ||| "piyo"
"piyo"

 

ついでに Python の and に相当する &&& も定義する。

(&&&) e1 e2 = if' e1 e2 e1

if’ , &&&, ||| を組み合わせて試すと、

*Main> if' ("hoge" &&& "piyo" &&& "fuga") 1 0
1
*Main> if' ("hoge" &&& "piyo" &&& "" &&& "fuga") 1 0
0
*Main> if' (1 &&& 2 &&& 3) 1 0
1
*Main> if' (1 &&& 2 &&& 0 &&& 3) 1 0
0
*Main> if' ("hoge" ||| "" ||| "piyo") 1 0
1
*Main> if' ("" ||| "") 1 0
0
*Main> if' (1 ||| 0 ||| 2) 1 0
1
*Main> if' (0 ||| 0) 1 0
0

全体のコードはこちら

 

Ruby でも同様に

Haskell では if を関数として定義し、型ごとにオーバーロードし、and, or に相当する関数を定義した。同じように Python でもできないかと考えたが、Python は組込クラスに手を出せない。 (+_+) それに対して、Ruby は組込クラスにメソッドを追加することができる。 (cf. リストと文字列を透過的に扱うには ) そこで、Ruby で同じように書けるか試してみることに。

Ruby では偽と解釈される値は false 以外に nil だけだった。これを Python と同じように Array, String, Integer でもそれぞれ偽と解釈される値を想定し、 if をメソッドとし実装。それに伴い and, or メソッドも追加し、or メソッドでデフォルトの値を設定できるようにする。

作成するモジュールとクラスは以下の通り。

img01-13-2010[1].png

Control モジュールで if メソッドを定義。これを使って、Bool モジュールで and, or メソッドを実装。イテレータを実装するときのようにテンプレートメソッドパターンを使って、各々のクラスで値による真偽のルールを記述する。

 

実装

まずは Control と Bool モジュールから。 Control モジュールの if メソッドは、真の値のときに評価されるブロック e1 と、偽の値のときに評価されるブロック e2 を受けとり、if メソッドが呼出されたオブジェクトの値に応じて e1 もしくは e2 を返す。 if_ メソッドは、その場で評価に応じてブロックを実行するようにした。 isTrue? と呼出しているメソッドは、このモジュールをインクルードする組込クラスで定義する。

Bool モジュールは Control モジュールで定義した if メソッドを使って and, or メソッドを定義。

module Control
  def if(e1, e2)
     if self.isTrue? then e1 else e2 end
  end
  def if_(e1, e2)
    self.if(e1, e2).call
  end  
end

module Bool
  include Control
  def and(other)
    self.if(other, self)
  end
  def or(other)
    self.if(self, other)
  end
end

後は、組込クラスで isTrue? メソッドを実装し、真偽と解釈される値の範囲を設定。

class TrueClass
  include Bool
  def isTrue?
    if self == true then true else false end
  end
end

class FalseClass
  include Bool
  def isTrue?
    if self != false then true else false end
  end
end

class NilClass
  include Bool
  def isTrue?
    if self != nil then true else false end
  end
end

class String
  include Bool
  def isTrue?
    if self != "" then true else false end
  end
end

class Integer
  include Bool
  def isTrue?
    if self != 0 then true else false end
  end
end

class Array
  include Bool
  def isTrue?
    if not self.empty? then true else false end
  end
end

まずは if と if_  メソッドを試してみる。変数 x, y に false, nil, "", 0, [] に入れて同じ動作をするか確認。if メソッドにおいて、オブジェクトの値によって設定した真偽が決定し、実行するブロックが決まる。

x = 0
y = 1

x.if(proc{ p "T" },
     proc{ p "F"}).call

x.if_(proc{ 
        p "T" 
        y.if_(proc{ p "TT" }, 
              proc{ p "FF" })
      },
      proc{ 
        p "F"
        y.if_(proc{ p "FT"}, 
              proc{ p "FF"})
      })

次に and, or を試す。

p true.and true                 #=> true          p 1.and 2.and 3                 #=> 3
p "hoge".and "piyo".and "fuga"  #=> “fuga”

p "hoge".or "piyo".or "fuga"    #=> “hoge”
p "".or "hoge"                  #=> “hoge”

ダックタイピングな Ruby なので、こんなのも書けてしまうか。(+_+)

p false.or 0.or "".or [].or 1000

もう何がなんだかわからなくなってきた。まぁ、これが書けたからどうというわけでもなく、あくまでもこんな風にも記述できるという実験ということで。 ^^;

コード全体はこちら

 

Scheme の and , or はどうなの?

そういえば、Scheme でも見かけは同じような動作をする。

(and 1 2 3)                  ;=> 3
(and "hoge" "piyo" "fuga")   ;=> “fuga”
(or 1 2 3)                   ;=> 1
(or "hoge" "piyo" "fuga")    ;=> “hoge”

Structure and Interpretation of Computer Programs によると、

  • (and <e1> ... <en>)

    The interpreter evaluates the expressions <e> one at a time, in left-to-right order. If any <e> evaluates to false, the value of the and expression is false, and the rest of the <e>'s are not evaluated. If all <e>'s evaluate to true values, the value of the and expression is the value of the last one.

  • (or <e1> ... <en>)

    The interpreter evaluates the expressions <e> one at a time, in left-to-right order. If any <e> evaluates to a true value, that value is returned as the value of the or expression, and the rest of the <e>'s are not evaluated. If all <e>'s evaluate to false, the value of the or expression is false.

    (太字は引用者による)

  • こういった評価の方法をするものを special form と言うらしいが、

    Each special form has its own evaluation rule. …

    the evaluation rule for expressions can be described by a simple general rule together with specialized rules for a small number of special forms. …

    Special syntactic forms that are simply convenient alternative surface structures for things that can be written in more uniform ways are sometimes called syntactic sugar,

    (Structure and Interpretation of Computer Programs より)

    Scheme 入門 15. 構文の定義 によると、

    マクロとは式の変換です。

    or, and はマクロで、以下のように再帰的に定義されています。マクロ定義も再帰的に定義できるので、かなり複雑な構文を定義することができます。

    Scheme はまだ全然わからないので、これは保留。ただし、式の評価方法という観点から、他の言語とは違うメタレベルを導入して定義してあるような気が。「ブール型だからこうだよ」というように型の視点からではなく。

     

    参考

    2010年1月7日木曜日

    if と真偽値

    Java の if 文での検査は真偽値のみ

    最初に馴染んだ言語が Java だったので、「if 文での検査で使えるのは当然 true または false でしょ」という感覚が染み込んでいる。例えば、Java で

    if (0){
     ...
    }

    をコンパイルしようとすると、

    Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - 互換性のない型
      期待値: boolean
      検出値:    int

    というエラーが表示される。

    Java言語規定 ブロック及び文 の 「14.8 if 文」によると、

    if ( Expression ) Statement

    … 式(Expression) は,論理型をもたなければならない。そうでなければ,コンパイル時エラーが発生する。

    (太字は引用者による)

    という仕様であるため。

     

    C 言語 の if 文での検査は 0 が 偽

    しかし、C 言語では上記のコードでもコンパイルが通る。なぜなら、初級C言語Q&A(5) によると、

    Q 【真偽値】

    他の言語には真偽の値を表現するための型が用意されているものがあるが、なぜC言語には用意されていないのか。

    … 最も大きな理由は、それがなくても実用上差し支えないからでしょう。実際、1という値を真、0という値を偽であることにすれば、整数型を使って真偽を表現することが可能ですから。

    この `0’  を偽 と見なすのは最初違和感があった。。 (@_@;

    数値の比較をしてみると、

    1 == 1

    で 1 が返り、

    1 < 1

    で 0 が返る。

    この仕様だと、自分でブール型を用意しなければ、真偽値を返すことを意図した関数と、数値を返す関数をコンパイラが区別できない。 (+_+)

    ただし、プログラミング言語 C の新機能 によると C99 で、

    今度の C 言語では新しい整数型 _Bool 型を導入することでその問題を解決します。この型は 0 と 1 が入れば十分な大きさとされており、必ずしも int 型と同じサイズであるとは限りません。

    となり、型的にはスッキリとする。

     

    Python, Ruby の真偽テスト

    Python はたくさんの値が 偽 と解釈される

    Python は 2.3.1 真値テスト で述べられているように、たくさんの型の値が条件文で 偽 として扱われる。例えば、リストと文字列の上位にシーケンス型があり、空文字・空リスト

    ”” , []

    は 偽 。(これにより、if の検査で リストと文字列を透過的に扱う ことができる。)

    これを「真偽テスト」というのは言い過ぎでないの?ってくらい多い。 ^^; バグを誘発しそうで怖い。。

     

    Ruby は false に加えて nil のみ

    Ruby はこれに対して、false に加え nil が偽と判定されるのみ。 Python 比べたらスッキリしている。ただし、Java で

    if (null) {

    なんて書いたらエラーになるので、これもやや違和感を覚えた。

    しかし、Ruby で上記のような書き方は実際に多用されているのだろうか?いや、そもそも多用されていたら Null Object パターン に持ちこむか。 JavaScript では DOM の操作でよく見るけれど。

     

    JavaScript の falsy

    4873113911JavaScript の場合は、

    以下に、条件式が falsy, つまり偽と評価される値を示す。

    • false
    • null
    • undefined
    • 空文字
    • 数値の 0
    • 数値の NaN

    (JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス, p.14 より)

    false ならぬ falsy という単語で述べられているように、Python に負けず劣らず 偽 と解釈される値が多い。 (@_@;

    (ちなみに、最初 JavaScript 見て嫌だなと思ったのがこの真偽テストに絡んでいる。「え?この if で何を確認してるの?」と、馴染まない内はイラつかされるオブジェクトの存否チェック、そしてネストネストでズラズラズラ…。あ~、どこに本質的な仕事が書かれているんだろう?とコードの森で迷い。。)

    総じて Script 系の言語は、さっと書くという利便性のために、「どこまでを if の検査で 偽 と見なすか」について設計思想が違うということかな。

     

    型とは

    4873112753話変わって、「型」とは何だっただろうか? 以前 にも引用したが 「データベース実践講義」 の 「2.3 型とは」 (p34) によると、

    型とはいったい何か。基本的には、値の名前付き有限集合である。…

    すべての型が、その型の値もしくは変数に作用する演算子の連想集合を持つ …

    とのこと。

    ある値の集合が型であり、その型に属する値に対して適用できる関数や操作が定義されるという見方。

     

    Java のプリミティブ型としての boolean

    Java に戻り、真偽値について確認してみると、

    boolean は,リテラル true 及び false (3.10.3) で示す二つの可能な値をもつ論理的な量を表す。

    (Java言語規定 型,値及び変数 の 4.2.5 boolean型 及び boolean値 より)

    値の集合が定義され、適用できる論理演算子が言語仕様として示されている。ただし、プリミティブ型である true, false が Java 言語で書ける枠内で、型に属する値として定義されているわけでない。 &&, || のような論理演算子についても同様。 Java を使う側は概念的イメージとして boolean 型を意識し、それに即してコードを書くとコンパイラが型をチェックしてくれる。

     

    C の真偽は数値

    前述の通り、C言語では C99 より前は真偽値に対応する型は用意されてなかった。真偽値として見なしたい値は数値なので、数値に対する演算子が適用できてしまう。 boolean 型として扱うとプログラマが意識したところで、コンパイラにとっては区別不能。

     

    Ruby の TrueClass , FalseClass

    Ruby では、

    Ruby の面白いのは、Boolean というクラスは無く、 true と false はそれぞれ TrueClass と FalseClass というクラスのインスタンスである。

    (「各言語におけるtrue/falseまとめ - 床のトルストイ、ゲイとするとのこと」 より)

    これ読むまで、てっきり Boolean というクラスがあると思っていた。 ^^;

    確認のため TrueClass のスーパークラスとインクルードしているモジュールを表示させてみると、

    p TrueClass.ancestors   #=> [TrueClass, Object, Kernel, BasicObject]

    となり、true と false だけをまとめる型は定義されていない。つまり、Ruby の言語の枠内でブール型というものが独立して明示されていない。だから、 and や or のようなものが、

    再定義できない演算子(制御構造)

    (演算子式 - Rubyリファレンスマニュアル より)

    として、何となく微妙な感じの位置付けになっているのかな?

     

    Python の真偽は数値のサブタイプ

    ついでなので Python についても調べたら、 3.2 標準型の階層

    ブール型 (boolean)

    … ブール型は整数のサブタイプで、ほとんどの演算コンテキストにおいてブール型値はそれぞれ 0 または 1 のように振舞います。ただし、文字列に変換されたときのみ、それぞれ文字列 "False" および "True" が返されます。

    うげぇ~ (@_@; 整数のサブタイプだったとは。。

    ということは、次の計算がエラーとならない。

    print True + True + False #=> 2
    print True * 10           #=> 10

    上記のような計算を成立させることの積極的な意味は何だろう???

    ともあれ、`0’ が if 文において 偽 と解釈される理由はこういう設計に依るのかも。どうしても ごちゃごちゃしているという感が否めない。 (+_+)

    PEP 285 -- Adding a bool type では次のように述べられている。、

    There's a small but vocal minority that would prefer to see
    "textbook" bools that don't support arithmetic operations at
    all, but most reviewers agree with me that bools should always
    allow arithmetic operations.

    6) Should bool inherit from int?

    => Yes.

    ということで、この設計はこのままずっと行くみたい。

     

    Haskell の Bool 型

    これに比べて Haskell はシンプル。 Data.Bool に、型とその値の集合が次のように定義されており、

    data Bool  = False  | True

    Bool 型に適用できる関数が示されている。

    (&&) :: Bool -> Bool -> Bool
    (||) :: Bool -> Bool -> Bool
    not  :: Bool -> Bool

    そして、条件式 である if – then – else の検査では Bool 型しか許されない。 case 式と同等なものとして以下のことが成り立つようになっている。

    if e 1 then e 2 else e 3 = case e 1 of { True -> e 2 ; False -> e 3 }

    ( The Haskell 98 Report: Expressions  3.6 Conditionals より)

    先ほどの 「型とは何か?」 という視点から見ると、なんてスッキリとしているんだろう。 ^^ 

    … とは言ったものの、最初は「何でこんなものが普通の関数と同じように定義されているの?」 と思ったけれど。 ^^;

     

    型ごとに if の解釈を変えたいならオーバーロードで

    もし、Python のように Bool 型に加え、空リスト、空文字、0 で False としたいなら、if-then-else とは異なる if’ 関数を定義し、それぞれの型でオーバーロードするのがいいかも。

    if を関数で定義するとは、表計算で、

    if (条件, 真の場合, 偽の場合)

    のように使うときのイメージで。 (cf. Data.Bool.HT の if’ 関数)

     

    まずは、値に適用したら Bool 型を返す isTrue 関数を持つ、型クラス Boolean を定義。

    class Boolean b where
        isTrue :: b -> Bool

    Bool, Integer, [a] 型を、型クラス Boolean のインスタンスにして、それぞれの型に応じた True , False を定義。

    instance Boolean Bool where
        isTrue True  = True
        isTrue False = False
    
    instance Boolean Integer where
        isTrue 0 = False
        isTrue _ = True
    
    instance Boolean [a] where
        isTrue [] = False
        isTrue _  = True

    これを使って if’ 関数を定義。 x の値が 真 と見なした値の場合 e1 が評価され、偽 の場合 e2 が評価される。

    if' x e1 e2 = if isTrue x then e1 else e2

     

    試しに使ってみる。数値の場合、

    *Main> if' 0 "hoge" "piyo"
    "piyo"
    *Main> if' 1 "hoge" "piyo"
    "hoge"

    リストの場合、

    *Main> if' [] "hoge" "piyo"
    "piyo"
    *Main> if' [1,2,3] "hoge" "piyo"
    "hoge"
    *Main> 

    リストで定義しているので、文字列の場合でもちゃんと動作する。

    *Main> if' "" "hoge" "piyo"
    "piyo"
    *Main> if' "test" "hoge" "piyo"
    "hoge"

     

    if’ を適用できる型を忘れてしまっても :i で調べられる

    上記の if’ 関数の型を調べると、

    *Main> :i if'
    if' :: (Boolean b) => b -> t -> t -> t

    もし、if’ 関数は何の型を 真・偽 と見なすか忘れてしまっても、 :i で 型クラス Boolean のインスタンスを表示できる。

    *Main> :i Boolean
    class Boolean b where t :: b -> Bool
    instance Boolean Bool
    instance Boolean Integer
    instance Boolean [a]

     

    if の解釈は所与のものであってほしくない

    上記のように if – then – else は ブール型の検査のみで、それ以外の値を 真・偽 と見なして評価したいなら別腹で定義する方が好み。これなら評価の方法を自分の好きなように任意の型で定義できる。

    Python でも同じように、クラスごとに真偽テストの挙動をカスタマイズできる。

    以下の値は偽であると見なされます …

    __nonzero__() または __len__() メソッドが定義されているようなユーザ定義クラスのインスタンスで、それらのメソッドが整数値ゼロまたは bool 値の False を返すとき

    (2.3.1 真値テスト より)

    (このメソッド名を見ても、あくまでも bool は int のサブタイプだぞ、覚えておけよ ! という気迫 を感じる。 ^^;)

    結局のところ、if で型ごとに 真偽 が異なることは、オーバーロードのことだと解釈できる。これを言語が所与のものしてしまうと、「一体それはどういう訳なんだろう?」と疑問がわき、頭の中がごちゃごちゃになり、すぐに忘れるこの脳みそ。 (+_+)

     

    Scheme

    最近ちょっといじりはじめた Scheme ではどうなっているか調べたら、Structure and Interpretation of Computer Programs によると、

    17 ``Interpreted as either true or false'' means this: In Scheme, there are two distinguished values that are denoted by the constants #t and #f . When the interpreter checks a predicate's value, it interprets #f as false. Any other value is treated as true. (Thus, providing #t is logically unnecessary, but it is convenient.)

    真偽は #t, #f の定数として定義され、#f のみが偽で、それ以外は真。 #t は実質的にはいらないと。

    うーーーん (@_@; 違和感を感じるなぁ~。

     

    関連記事

     

    参考サイト

    2009年10月31日土曜日

    Haskell の Maybe a 型と Either a b 型 (1) - 「結果を返しそこなう計算」、 Java と比較しながら

    091029-011

    Bool に並ぶ基本的な型

    Prelude の冒頭「Basic data types」には、次の 3 つの型が挙げられている。
    Bool 型については、他の言語でも基本的な型としてあるので問題ない。しかし、「Maybe, Either って何だこりゃ?」というのが最初の印象。
    何よりも Prelude の最初に記述されているということは重要なんだうろと思ったけれど、例えば次のような説明を読んでも、
    … Haskell の Maybe 型についてはよく馴染んでいると思いますが、
    data Maybe a = Nothing | Just a

    これは結果を返しそこなうかもしれない計算の型を表現しています。
    (モナドとは何か より, 太字は引用による)

    は? (@_@;) 「結果を返しそこなうかもしれない計算の型」ってどんな型?という疑問が。なぜなら、例えば、自分で定義する 代数的データ型 Person であるなら、それに対応した「人」のイメージを思い浮かべることはできるし、また、真偽値を表わす Bool 型や整数を表わす Int 型も抽象的な概念であるけれど、馴染みがあるので想像の範疇を超えない。それに対して「結果を返しそこなうかもしれない計算」というのは、一体どういうもので、何を具体的に想像すればいいのかわからなかった。

     

    Python や Ruby で結果を返しそこなうとは - index

    Python の場合
    Haskell に出会うまでは、それを一つの概念として認識することはなかった。例えば、Python で Haskell のリストに類似した 変更可能なシーケンス型 には index メソッドが定義されている。(cf. Python の シーケンス型に慣れる)

    s.index(x[, i[, j]])
    s[k] == x かつ i <= k < j となる最小の k を返します。

    `注’ には「xs 中に見つからなかった場合 ValueError を送出します。」と書かれている。
    例えば、以下のコードを実行すれば、
    ary = [1,2,3]
    print ary.index(1)
    print ary.index(4)
    次のような結果が表示される。
    0
    Traceback (most recent call last):
      File "index.py", line 3, in 
        print ary.index(4)
    ValueError: list.index(x): x not in list

     

    Ruby の場合
    Ruby でも同様に、Array の index メソッドの説明には、

    等しい最初の要素の位置を返します。…
    等しい要素がひとつもなかった時には nil を返します。

    次のコードを実行。
    ary = [1,2,3]
    p ary.index(1)   #=> 0
    p ary.index(4)   #=> nil

    Enumerable の find メソッドも動作は似ている。(cf. Python のイテレータ (2) - Ruby の Enumerable との比較)
    上記のようなメソッドは、検索して見つけた場合にはそれに関する情報を返し、見つからなかったら「何もなかったよ」という意味で nil やらエラーを返すという動作をすると理解していた。決して「見つかった場合と見つからなかった場合」を総称する概念が頭の中にあったわけではない。同じメソッドなんだけれど、場合によって返されるものは全く別物と言う感覚。

     

    Haskell における具体例 - elemIndex

    これに対して Haskell では、上記と同様の関数である Data.List の elemIndex の型を見ると、Mabye Int 型が返される。

    elemIndex :: Eq a => a -> [a] -> Maybe Int

    次のコードを実行。
    import Data.List
    main = do let l = [1..3]
              print $ elemIndex 1 l   -- Just 0
              print $ elemIndex 4 l   -- Nothing

    Ruby や Python で nil やエラーを返す代わりに、Nothing という値を返していることがわかる。重要なのは Nothing という値が Maybe Int 型であること。つまり「あった場合と、なかった場合」をまとめた概念として Maybe  a 型を想定している。
    もし、検索した結果「あった・なかった」だけわかればいいのであれば、Bool 型の True, False の 2 値で代用できる。しかし、Maybe a 型のデータコンストラクタ Just は、elemIndex 関数で言うなら「見つけたインデックス」という具体的な値を`Just という容器’に入れて持ってきてくれる。
    さすが型にキッチリカッチリした Haskell らしいやり方と思ったけれど、関数の型を見ればその意図を汲み取れるというのはメリットがある。逆に「見つからなかったとき」どうなるのか、ドキュメントを読まなければわからない方が気持ち悪く思えてきた。 (@_@;) これは Java にしてもそうで、List インターフェイスの indexOf の返す型を見ただけでは、「見つからなかった」ときどうなるのかはわからない。(まぁ、もちろん慣習から想像できるけれど。。)

     

    Java の null は忍び込む

    静的な型チェックをしてくれる Java だけれど、null に対してはコンパイル時にスルーされる。例えば、「人」が「名前」「住所」の情報を持っており、「人」のリストから「名前」で検索し、「住所」を得たいとする。次のように Java で書いてコンパイルしてもエラーは出ない。
    import java.util.Arrays;
    import java.util.List;
    
    public class Search {
    
        private static List<Person> persons = Arrays.asList(
                new Person("Tarou", "Tokyo"),
                new Person("Jirou", "Osaka"),
                new Person("Saburou", "Nagoya"));
    
        static Person find(String name) {
            for (Person p : Search.persons) {
                if (p.getName().equals(name)) {
                    return p;
                }
            }
            return null;
        }
    
        public static void main(String[] args) {
            System.out.println(find("Tarou").getAddress());
            System.out.println(find("Hanako").getAddress()); // NullPointerException
        }
    }
    
    class Person {
    
        private String name;
        private String address;
    
        Person(String name, String address) {
            this.name = name;
            this.address = address;
        }
    
        String getName() {
            return this.name;
        }
    
        String getAddress() {
            return this.address;
        }
    
        @Override
        public String toString() {
            return this.name + ":" + this.address;
        }
    }
    しかし、コードを実行すると NullPointerException が発生する。理由は以前見たように、

    null の型である特別な 空型(null type) も存在する。それには名前がない。空型には名前がないので,空型の変数を宣言すること又は空型にキャストすることはできない。空型の式が取りうる唯一の値が空参照となる。 空参照は,常に任意の参照型にキャストできる。
    (Java言語規定 型,値及び変数 より)

    という仕様による。
    つまり、null を返すことは、どこかでキッチリ null のチェックすることが要求され、実行時になって気がつくという危険性が潜むことになる。

     

    Haskell はコンパイル時にエラーで教えてくれる

    これに対して、Haskell で上記と同じような内容を書こうと思い、間違えて次のように書いたとする。
    import Data.List
    
    data Person = Person { name, address :: String} deriving Show
    
    ps = [ Person "Tarou" "Tokyo"
         , Person "Jirou" "Osaka"
         , Person "Saburou" "Nagoya"
         ]
    
    getAddress n = address . find ((== n) . name)
    
    main = putStrLn $ getAddress "Tarou" ps
    これを実行しようとしても、その前にエラーが表示される。
        Couldn't match expected type `Person'
               against inferred type `Maybe Person'
        In the second argument of `(.)', namely `find ((== n) . name)'
        In the expression: address . find ((== n) . name)
        In the definition of `getAddress':
            getAddress n = address . find ((== n) . name)
    Failed, modules loaded: none.

     

    エラーの読み方
    いつもちゃんと読もうと思っているのだけれど、おざなりに。 (+_+) 今回はちゃんと読むことにした。 ^^;
    とりあえず、下から上へと読むのがわかりやすい。エラーの原因となっている場所が、「定義 → 式 → 引数 」と位置が絞り込まれ、自分が書いた式が返す型に対して、

    「この場合だと、本当はこの型が来るんですけどねぇ。。」

    とコンパイラが教えてくれる。
     

    091030-001


    今回は address の使い方を間違えているので、Maybe Person 型を扱えるように変更しなければならない。

     

    Maybe a の型に応じた処理を実装
    上記の getAddress 関数を以下のように修正。Maybe は Just と Nothing なので、それに応じて処理を分ければよい。
    getAddress n ps = case find ((== n) . name) ps of
                        Just x  -> address x
                        Nothing -> n ++ " ha imasen"
    
    main = do putStrLn $ getAddress "Tarou" ps
              putStrLn $ getAddress "Hanako" ps

    ちなみに、Haskell だと重複があるとなるべく排除したい気分になるので、main 関数を次のように変えてもよい。
    main =  mapM_ (putStrLn . flip getAddress ps) ["Tarou","Hanako"]

    コード全体はこちら

     

    Java で null を返す代りに例外を投げる

    ついでなので、Java ではどのようにしてコンパイル時にエラーを吐いてくれるようにするか考える。
    Emulating Haskell's Maybe in Java with Checked Exceptions には例外を使ってコンパイル時にチェックする方法が述べられている。これを真似して上記の Java コードを修正する。
    要は、find メソッドにおいて、見つからなかったときに null を返すのはなく、例外を投げるということ。これにより、クライアントが例外をチェックしてなかったら、コンパイラがエラーを表示してくれると。
    まずは、Exception クラスを拡張して Nothing を作成。
    class Nothing extends Exception {
    
        Nothing(String name) {
            super(name);
        }
    }
    Search クラスの find メソッドにおける null を、例外 Nothing で置き換える。
        static Person find(String name) throws Nothing {
            for (Person p : Search.persons) {
                if (p.getName().equals(name)) {
                    return p;
                }
            }
            throw new Nothing(name);
        }
    main メソッドで例外を捕捉。
        public static void main(String[] args) {
            try {
                System.out.println(find("Tarou").getAddress());
                System.out.println(find("Hanako").getAddress());
            } catch (Nothing e) {
                System.out.println(e.getMessage() + " ha imasen");
            }
        }

    これで、try .. catch で捉えないとコンパイル時に怒られるようになった。
    コード全体はこちら

     

    Java で Null オブジェクトパターンを使う



    4894712288
    もう一つ、null のチェックをなくしたいと言えば、連想するのはリファクタリングの中で紹介されていた Null Object パターン。(p.260) (cf. Null Object pattern – Wikipedia)
    これは、Person クラスを拡張して、検索して見つからなかった場合を表現する NullPerson クラスを作成。ここに null であった場合の振舞を集約する。
    class NullPerson extends Person {
    
        NullPerson(String name) {
            super(name, "");
        }
    
        @Override
        String getAddress() {
            return super.getName() + " ha imasen";
        }
    }
    Search クラスの検索メソッドでは、見つからなかった場合、上記の NullPerson クラスのオブジェクトを返す。
        static Person find(String name) {
            for (Person p : Search.persons) {
                if (p.getName().equals(name)) {
                    return p;
                }
            }
            return new NullPerson(name);  // null の代わりに Null オブジェクトを返す
        }
    これにより、main メソッドで null チェックをしなくても済むようになる。
        public static void main(String[] args) {
            System.out.println(find("Tarou").getAddress());
            System.out.println(find("Hanako").getAddress());
        }

    コード全体はこちら

     

    Java で Haskell の Maybe a 型を真似る

    ついでに、Java の総称型について知識がないので正しい記述なのかわからないが、Haskell の Maybe a 型に相当するクラスを試しに書いてみる。今回は IDE に Netbeans を使っているので、こういうとき IDE が「あれ違うこれ違う」と教えてくれるので、とりあえず動くものが書けるのでありがたい。 ^^
    091031-002まずは Maybe a 型に対応したクラスを書く。Java ではパターンマッチによって、コンストラクタで作った中身を取り出せないので、Maybe<T> クラスに fromJust メソッドを実装。これにより Just で包んだ中身の値を取り出す。
    class Maybe<T> {
     
        protected T a;
     
        T fromJust() {
            return this.a;
        }
    }
     
    class Nothing extends Maybe {
    }
     
    class Just<T> extends Maybe {
     
        Just(T a) {
            this.a = a;
        }
    }
    Person オブジェクトを検索するメソッドでは、見つかったとき Just クラスのオブジェクトで包み、見つからなかったら Nothing クラスのオブジェクトを返す。
        static Maybe<Person> find(String name) {
            for (Person p : Search.persons) {
                if (p.getName().equals(name)) {
                    return new Just(p);    // 見つかったら Just で包む
                }
            }
            return new Nothing();  // Haskell の Maybe a 型の値 Nothing に相当
        }
    これにより、find メソッドを使う getAddressメソッドでは、Maybe<Person> で受けなける必要に迫られ、Just と Nothing に応じた処理を書く。
        static String getAddress(String name) {
            Maybe<Person> m = find(name);
            if (m instanceof Just) {
                return m.fromJust().getAddress();
            } else {
                return name + " ha imasen";
            }
        }

    コード全体はこちら

     

    余談

    と、ここまで書いたとき、以前に同じようなことを書いたような気がしたので検索したら、「Haskell の Maybe 型に馴染む」が見つかった。内容については全く記憶から抜けていたにも関わらず、冒頭が今回とほぼ同じだったのにはさすがに笑った。 ^^; そのときはまだ Maybe と Either の類似性には全く気がつかず。Java における null を、どのようにして Haskell では扱うのだろうか?に関心があったようだ。続く「Maybe 型に馴染む (2)」を見ても、モナドについては全くイメージができていなかったことが伺える。やはり、「State モナド (1) - 状態を模倣する」を書いた以前と以後では関数を見る見方が少し違ってきたように思う。

     

    関連記事