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) - 状態を模倣する」を書いた以前と以後では関数を見る見方が少し違ってきたように思う。

 

関連記事

2009年10月30日金曜日

Java でオブジェクトの比較とソート

Haskell の代数的データ型を比較、特定の基準でソート を試したので、ついでに Java でも。てゆうか、総称型が導入されてから、JDK で言うと 1.5 以降 Java を触ってないので API のドキュメントを読んでも隔世の感が。。 ^^;

 

「人」クラスの定義

前回と同様、「人」が「名前」「年齢」を持っているとして、

public class Person {

    private String name;
    private int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    String getName() {
        return this.name;
    }

    int getAge() {
        return this.age;
    }

    @Override
    public String toString() {
        return this.name + ":" + this.age;
    }
}

 

Comparable<T> を実装して比較可能に

Person クラスを比較可能にするには、Comparable インターフェイスを実装。

インタフェース Comparable<T>

型パラメータ:
T - the type of objects that this object may be compared to

昔は <T> なんてなかったので、久しぶりに見ると混乱するなぁ~ (+_+)

Haskell で言うなら、

class Eq a => Ord a where

の型変数 a に相当するということか。

 

クラス宣言において、Comparable を implements するとき、後ろに <具体的なクラス名> を入れる。

public class Person implements Comparable<Person> {

Haskell なら、

instance Ord Person where

 

Comparable<Person> を実装するには、compareTo メソッドを書く必要がある。ここでは「人」の年齢が人の順序を決めるものとして扱う。総称型を利用することによって、Object クラスをキャストして使わなくて済むところが以前より楽。

    public int compareTo(Person o) {
        return new Integer(this.age).compareTo(o.getAge());
    }

これで Personクラスのオブジェクトを比較できるようになった。

System.out.println(new Person("Tarou", 10).compareTo(new Person("Saburou", 30)));  // -1

 

ソート

次に上記で定義した比較によるソートを試す。リストを作成するには、ArraysasList を使うのが書きやすい。

public static <T> List<T> asList(T... a)

このメソッドも随分様変りしてるなぁ。。。以前は、

public static List asList(Object[] a)
(Arrays (Java 2 プラットフォーム SE v1.4.0) より)

引数の … は可変長の引数を表わすらしい。(cf. ライトニングJava (9) 可変長引数(3)) static の後の <T> は何を表わしているのか知らないけれど、とにかく使う分には、

        List<Person> persons = Arrays.asList(
                new Person("Saburou", 30),
                new Person("Jiro", 20),
                new Person("Tarou", 10),
                new Person("Hanako", 30));

これをソートするには、Collectionssort を使う。

        Collections.sort(persons);
        System.out.println(persons);   // [Tarou:10, Jiro:20, Saburou:30, Hanako:30]

しかし、型を見ると、

public static <T extends Comparable<? super T>> void sort(List<T> list)

うげぇ (@_@;) ますます分からん。。

 

Comparator でソートする基準を変更

ソートする基準を変えたい場合は、Comparator を使う。

例えば、「年齢が同じ場合、名前順にしたい」なら、

        Collections.sort(persons, new Comparator<Person>() {
            public int compare(Person o1, Person o2) {
                int c = new Integer(o1.getAge()).compareTo(o2.getAge());
                if (c != 0) {
                    return c;
                } else {
                    return o1.getName().compareTo(o2.getName());
                }
            }
        });
        System.out.println(persons);   // [Tarou:10, Jiro:20, Hanako:30, Saburou:30]

これも Comparator<Person> とクラスを指定すると、compare メソッドの引数のクラスが決まるようで、キャストが必要なくなっている。

 

あ~、Java の総称型は完全にスルーしていたので読めない。。パタッ(o_ _)o~†

しかし、これからは Scala の時代になっていくのかなぁ?

 

全体

2009年10月27日火曜日

復習 「ふつうのHaskell」 2章

4797336021

ファイルの行数を数える

countline.hs (p.39)

短かいので do 式は使わずに、

main = getContents >>= \x -> print $ length $ lines x

関数合成を利用すると、(cf. Haskell の関数合成)

main = getContents >>= print . length . lines

 

ファイルの先頭から指定した行数だけ表示

head.hs (p.45)

こちらも do 式を使わず、

main = getContents >>= putStrLn . getlines 10
getlines n = unlines . take n . lines

 

ところで、これは次のようにも書ける。

main = getContents >>= printLines 10
printLines n = putStrLn . unlines . take n . lines

一体どちらのコードがいいのかな?

Introduction to QuickCheck – HaskellWiki によると、

… the side effecting monadic code is mixed in with the pure computation, making it difficult to test without moving entirely into a "black box" IO-based testing model.

先ほどの関数の型を比較すると、

*Main> :t getlines
getlines :: Int -> String -> String
*Main> :t printLines
printLines :: Int -> String -> IO ()

前者のの方が IO モナドを含まず、純粋な関数なのでテストしやすくて良いようだ。

 

ファイルの末尾から指定した行数を表示

tail.hs (p.51)

head.hs と同じようにして、

main = getContents >>= putStrLn . tailLines 3
tailLines n =  unlines . reverse . take n . reverse . lines

 

しかし、tailLines 関数を見ると、関数 lines – unlines, reverse – reverse が対になっており、真ん中に take 関数が置かれているように見える。関数を関数で挟みこむデコレータのような関数を定義してシンプルにできないものか…。(cf. Python のデコレータ式 (1))

次のように関数 f を 関数 x, y で挟む関数を次のように定義してみる。

(***) x y f z = y $ f $ x z

これは関数合成を使った方が見やすいかも。

(***) x y f = y . f . x 

これにより対になる関数を定義。

l = lines *** unlines
r = reverse *** reverse

上記の関数を試してみる。

*Main> l (r $ take 2) "hoge\npiyo\nfuga\n"
"piyo\nfuga\n"

動作したので takeLines を書きなおすと、

tailLines n = l $ r $ take n

関数合成を使うなら、

tailLines = l . r . take

 

全体は、

main = getContents >>= putStrLn . tailLines 3

(***) x y f = y . f . x 

l = lines *** unlines
r = reverse *** reverse

tailLines = l . r . take

うーん、返ってわかりずらくなったような気が… (@_@;)  QQQ

2009年10月26日月曜日

Haskell のリスト定義はなぜあの形? - 素朴な解釈

空リストって何?

以前、「Haskell の cons (コンス) - リストを生成するための演算子 (:)」で、リストの定義とその構造を支えている cons について触れた。

これを書いたことすら忘れかけていた今日この頃、「やさしい Haskell 入門」におけるリストの定義を読んでいたら、また素朴な疑問がわいた。(@_@;)

リストも同様に簡単に定義できます。リストの場合は再帰的になっているのが面白いですね。
data [a]               = [] | a : [a]                  -- more pseudo-code
[]は空リストであり、:はリストの中置構築子です。つまり、 [1,2,3]1:2:3:[]と同等であるはずです。(:は右結合性をもちます。) [] の型は [a] で、 : の型は a->[a]->[a] です。

(2.4 組み込みの型は特別な型ではない より)

疑問は次の 3 つ。

  • 要素が 0 の空リストってどんなイメージ?
  • 要素がないのになぜリスト?
  • なぜ空リストを想定しないとリストを定義できないの?

全て「空リスト」に関連している。

 

素朴に考える

リストと言われイメージするのは、要素が連なっているもののこと。

091025-005

最初に、最小のリストについて考える。もし、リストの要件として、「要素が連なっている」ことが必須であるならば、最小の連なりは二つの要素。

091025-006

これを代数的データ型で定義すると、

data List a = TwoElem a a

二つの要素を持つ List a 型の値を生成するには、

TwoElem 1 2

 

次に、3 つ以上の要素の連なりを表現するには再帰的な定義をする。

data List a = TwoElem a a
            | List (List a) a

3 つの要素を持つ List a 型の値を生成するには、

List (TwoElem 1 2) 3

4 つの要素の場合は、

List (List (TwoElem 1 2) 3) 4

 

見方を変える

ここで、リストを「要素の連なりである」という発想を忘れ、要素を入れる「入れ物」と考えるのであれば、要素が 1つ、要素が 0 の状態を想定したとしても不自然ではない。

data List a = TwoElem a a
            | List (List a) a
            | OneElem a
            | Empty

しかし、この定義は冗長。

なぜなら、リストに要素が一つの状態を表現するのに、

OneElem 1

と書けるけれど、

List Empty 1

で代用できる。よって、データコンストラクタ OneElem は必要ない。

同じように、リストに要素が二つある状態 TwoElem 1 2 は、

List (List Empty 1) 2

で代用できるので、データコンストラクタ TwoElem も不要。

結局残ったのは、

data List a = List (List a) a
            | Empty

上記の定義は、要素をリストの後ろに追加していくと解釈できる。要素をリストの後ろに追加していくことと、前に追加していくことは、リストの構造にとって本質的な意味を持たないので、以下のように定義したとしても同じ意味。

data List a = List a (List a)
            | Empty

以前書いたように、こちらの方が扱いが自然な感じがするので、多分この形になったのではないのかな?

 

関連サイト

関連記事

2009年10月24日土曜日

Google Chrome でマウスジェスチャー - タブを閉じる動作を割り当てる

1. タブを閉じる操作をマウスジェスチャーに割り当てたい

Firefox でマウスジェスチャーは、Mouse Gestures Redox を使っている。唯一利用している動作は「タブを閉じる」。ブラウザを利用していて一番使う操作なので、一番シンプルな`下方向’への動作を割り当てている。

091024-003Google Chrome への依存が高まるにつれ、不便さを感じるのはこのマウスジェスチャー。キーボードに手を置いていれば Ctrl + W で閉じるのだけれど、マウスジェスチャーでさっと閉じれないのはストレス。 (+_+)

どうにかならないかと思っていたら、MOONGIFT: » Google Chromeにマウスジェスチャーを「nk-gesture」 によると、

nk-gestureがGoogle Chrome用のマウスジェスチャーソフトウェアだ。機能拡張として動作するので、起動時に–enable-extensionsを付けて起動する必要がある。

自分は Chrome の開発版を使っているので、特に問題なく使えそうだ。(cf. Google Chrome で pbtweet – User Script を使って)

 

2. nk-gesture のインストール

nk-gesture - Project Hosting on Google Code よりダウンロードして、実行するとインストール完了。

 

3. 設定画面を開く

新規タブを開き、カクカクとした?マークを描く。

 

English Language にチェックを付ける。

091024-001

 

4. ジェスチャーの設定

上下左右の動作を UDLR で表現するようなので、下方向への動作を割り当てるには、Close tab で `D’ を入力して OK ボタンを押した。

091024-004

これでまた一歩 Chrome への依存が… ^^;

追記(2013/03/25): 現在、マウスジェスチャーは、Gestures for Chrome(TM) を利用している。

2009年10月23日金曜日

Haskell の代数的データ型を比較、特定の基準でソート – compare, sortBy

Ruby であるクラスのオブジェクトを比較可能にするには、クラスに Comparable モジュールをインクルードする。Python なら __cmp__ メソッドをクラスに実装
同じように Haskell でも代数的データ型を比較できるようにするには、Ord クラスのインスタンスにする。

比較できるように

代数的データ型の定義
例えば、「人」が「名前」「年齢」を持つことを代数的データ型で表現すると、
data Person = Person { name :: String
                     , age  :: Int
                     } deriving Show

Ord クラスのインスタンスにする
Data.Ord によると、
Minimal complete definition: either compare or <=. Using compare can be more efficient for complex types.
よって、compare メソッドを実装。ただし、ここでは「人」を比較可能にしたとき、その「年齢」のみで順序が決まるものとする。
instance Ord Person where
       compare x y = compare (age x) (age y)
比較対象の「年齢」を表わす age は Int 型で、これは既に Ord 型のインスタンスなので compare メソッドで比較した結果をそのまま返した。

Eq クラスのインスタンスにする必要も
しかし、このままではエラーが表示されてしまう。 (+_+)
    No instance for (Eq Person)
      arising from the superclasses of an instance declaration
Ord クラスは以下のように Eq クラスを継承している。そのため、Person 型は Eq のインスタンスでなくてはならない。
class Eq a => Ord a where
(Data.Ord より)
例えば、同じ年齢なら同値とみなすとしておくなら、
instance Eq Person where
    x == y = age x == age y
(単に名前も含めて同値とするなら、Person 型の定義において導出インスタンスを使い deriving (Show, Eq) のようにする。)
具体的な値で比較してみる。
*Main> Person "Tarou" 30 < Person "Hanako" 30
False
*Main> Person "Tarou" 30 <= Person "Hanako" 30
True
*Main> Person "Tarou" 30 < Person "Hanako" 10
False
*Main> Person "Tarou" 30 > Person "Hanako" 10
True

ソート

Data.Listsort の定義は、
sort :: Ord a => [a] -> [a]
Person 型を Ord クラスのインスタンスにしたので、[Person] に sort を適用できるようになった。ps を以下のように定義して、
ps = [ Person "Saburou" 30
     , Person "Jiro" 20
     , Person "Tarou" 10
     , Person "Hanako" 30
     ]
ソートすると、
*Main> sort ps
[Person {name = "Tarou",   age = 10},
 Person {name = "Jiro",    age = 20},
 Person {name = "Saburou", age = 30},
 Person {name = "Hanako",  age = 30}]

特定の基準でソート

しかし、上記のように定義した場合、インスタンス宣言したときの比較に固定されてしまう。色々な基準でソートしたいなら、Data.ListsortBy 関数で比較方法を引数として渡してソート。
例えば、名前で比較させたい場合、
sortBy (\x y -> compare (name x) (name y)) ps
順序を逆にしたい場合は、x と y を入れかえて、
sortBy (\x y -> compare (name y) (name x)) ps

comparing 関数
Data.Ord には、こういうときに便利な comparing 関数が定義されている。
comparing p x y = compare (p x) (p y)
これを使うと、
sortBy (comparing name) ps
と書くことができる。

flip で逆順
順序を逆にしたい場合は、flip 関数を使い引数を入れかえる。
sortBy (flip $ comparing name) ps

複数の基準でソート
次に、「年齢順に並べるが、同じ年齢の場合は名前順にしたい」のなら、
  print $ sortBy cmp ps where
                 cmp x y = let c = comparing age x y
                           in if c /= EQ then c
                              else comparing name x y

複数の基準をつなげる

ここで、「人」の属性に「性別」も加える。
data Gender = Male | Female deriving (Show, Eq, Ord)

data Person = Person { name   :: String
                     , age    :: Int
                     , gender :: Gender
                     } deriving Show
「性別、年齢、名前」の順にソートするなら、
  print $ sortBy cmp ps where
                 cmp x y = let c = comparing gender x y
                           in if c /= EQ then c
                              else let c = comparing age x y
                                   in if c /= EQ then c
                                      else comparing name x y
先ほどの [Person] 型のリストの内容を以下のように変更。
ps = [ Person "Youko"   30 Female
     , Person "Saburou" 30 Male
     , Person "Jiro"    20 Male
     , Person "Hanako"  30 Female
     , Person "Tarou"   10 Male
     ]
これを上記でソートした結果、
[Person {name = "Tarou",   age = 10, gender = Male},
 Person {name = "Jiro",    age = 20, gender = Male},
 Person {name = "Saburou", age = 30, gender = Male},
 Person {name = "Hanako",  age = 30, gender = Female},
 Person {name = "Youko",   age = 30, gender = Female}]

つなげる関数

それにしても、上記のコードは let, if, let, if,… と長い。 (+_+) これを 「Haskell の State モナド (1) - 状態を模倣する」のように、つなぎの部分を部品化しておき、個々のパーツをくっつけることはできないだろうか?
イメージとしては、次のような形。
  print $ sortBy cmp ps where
                 cmp = comparing gender `comb` 
                       comparing age    `comb` 
                       comparing name

つなぎの関数名を comb とする。最初の二つの関数 comparing gender, comparing age に注目。この二つを引数に取ることを想像すると、
comb cmp1 cmp2 = ...
comparing gender 関数は、比較のための引数を二つ取るので、
comb cmp1 cmp2 = ...             cmp1 x y 
cmp1 の結果を変数に束縛する。
comb cmp1 cmp2 = ...     let c = cmp1 x y 
cmp1 で x, y を引数として取るには、x, y が与えられている必要があるので、無名関数の形で、
comb cmp1 cmp2 = \x y -> let c = cmp1 x y 
cmp1 において比較した結果、同値と見なされなかったら、その比較をこの関数の結果とする。
comb cmp1 cmp2 = \x y -> let c = cmp1 x y 
                         in if c /= EQ then c
そうでなければ、cmp2 で比較する関数を結果とする。
comb cmp1 cmp2 = \x y -> let c = cmp1 x y 
                         in if c /= EQ then c else cmp2 x y

これで先ほどの comb でつなげた関数を実行できるようになる。結果は、
[Person {name = "Tarou",   age = 10, gender = Male},
 Person {name = "Jiro",    age = 20, gender = Male},
 Person {name = "Saburou", age = 30, gender = Male},
 Person {name = "Hanako",  age = 30, gender = Female},
 Person {name = "Youko",   age = 30, gender = Female}]

2009年10月20日火曜日

ウィンドウを切り替える「窓替え」で Migemo を使う

タスク切り替えユーティリティ「窓替え」を見やすい設定に」の続き。

 

Migemo

Migemo はローマ字のまま日本語をインクリメンタル検索するためのツールです。

(Migemo より)

Firefox でもアドオンを使って利用。

 

窓替えの設定

窓替え」でも Migemo を使うための設定があったのでこれを使うことに。なぜなら、たくさんのウィンドウを開いていると、目で見て探すのが負担になるため。(+_+) 検索した方が余程早い。それに Migemo が使えるなら言うことなし。

以下、窓替えのある C:\Program Files\madokae_XXXXX フォルダ内の「readme.txt」の 「9.2. Migemoを使ったインクリメンタルサーチ」を見て設定した。

 

インストール

以下をダウンロードして解凍。

C:\Program Files\madokae_XXXXX フォルダ内に必要なものを置く。

091020-004

 

Migemo の利用

タスクトレイ > 窓替え > オプション > 窓替えの設定 > その他 > Migemo において、「Migemo を使用する」にチェック。すぐに Migemo での検索がされるように「閾値」を 1 に設定。

091020-001

 

窓替え起動と同時に Migemo による検索

これまでは Alt + Q で窓替えを起動するようにしてたけれど、起動と同時に Migemo で検索したい。

窓替えの設定 > ユーザーインタフェース > ホットキー において、「タスクトレイ入れる/出す|アクティブにする」のホットキーをクリアし、代わりに「I-searchモードでアクティブにする」に Alt + Q を割り当てた。

091020-002

 

Emacs ライクな移動

ユーザーインタフェース > ショートカットキーの設定を見たら、カーソルの移動にショートカットが付いていた。デフォルトで Meadow と同じ操作なのでこのままにしておく。これでカーソルまで手を伸ばす手間が省ける。 ^^

091020-003

 

インクリメンタルサーチ をした後、移動

例えば、「切り替えたいウィンドウの名前が思い出せないけれど、explorer のウィンドウのいずれか」を表示させたい場合、

  1. `exp’ で Migemo によるインクリメンタルサーチ
  2. Ctrl + N などで移動

によって素早く切り替えることができる。

カーソルの移動をした後は、インクリメンタルサーチモードが終了するので、ウィンドウ名の先頭についている英字を押すことによってアクティブにできる。

2009年10月19日月曜日

Firefox のアドオン Migemo によるページ内検索

アドオンをたくさん入れていると、何がどの機能か忘れてしまう。 ^^;

つい最近まで Firefox の標準の機能だと勘違いしていたのが

のページ内検索によるハイライト。

  1. Ctrl + F を入力
  2. 検索したい単語を入力
  3. Alt + A で「すべてを強調表示」

090918-003.png

検索した単語の位置をウィンドウの右に示してくれるので、ブラウザでコードを見るとき重宝している。

090918-002.png

 

デフォルトのページ内検索

久しぶりに Firefox のプロファイルを新しく作り、アドオンをインストールしていない状態で、上記と同様な動作をしたら、

090918-001.png

あれ?いつもと表示が違うのはなぜ?と悩んでしまった。 (@_@;)

 

検索時の操作

しかも、最近になってはじめて気がついたのは、単語を選択した後に Ctrl + F をした時点でページ内検索のフィールドに単語がコピーされること。

今までしていた操作

実際にはこれだけで済む

  1. 単語を選択
  2. コピー
  3. Ctrl + F
  4. ペースト
  5. Alt + A で「すべてを強調表示」
  1. 単語を選択
  2. Ctrl + F
  3. Alt + A で「すべてを強調表示」

コピーからペーストまでの一連の動作が習慣化していたために、コピーが必要ないことに気づいてなかった。

ちなみに、「すべてを強調表示」が有効になっている限り、他の単語を強調表示したい場合、当該の単語を選択した後 Ctrl + F だけで連続して検索できる。

 

Migemo で長い文章の検索

基本的な操作ですら気がついてなかったことがあるので、XUL/Migemo でも色々とあるはずだと思い調べてみたら、

… 「nihongo deno pe-zi nai kensaku」と、スペースキーで言葉を句切りながらローマ字入力してみてください。「日本語でのページ内検索」という文章が検索されるはずです。…

いわゆるSKK方式での入力もサポートしています。「nihongoDenoPe-ziNaiKensaku」…

(長い文章の検索 より)

なるほど、これで文章を検索できるとは。

 

関連記事

2009年10月16日金曜日

「ブログへの自分以外のつぶやきを知る」ための Yahoo Pipes の作り方

前回作ったパイプ (Pipes: Filter backtweets by twitter username) の作り方。

 

全体の構成

全体のパイプの大雑把な構成は以下の通り。6 つの小さなパイプをつないでいる。

091016-001

※ 点線は概念的なイメージ

 

backtweets.com から RSS を取得しフィルタリング

パイプ : Filter backtweets by twitter user name (No Image)

フィードの取得

使用するモジュール

  • User inputs > URL Input : 入力
  • URL > URL Builder : URL の生成
  • Sources > Fetch Feed : フィードの取得

091016-006

まず、BackTweets においてブログの URL で検索 ( http://jutememo.blogspot.com — BackTweets )し、RSS のアドレスを表示させる。

http://backtweets.com/search.rss?q=http://jutememo.blogspot.com

これより、この RSS を Fetch Feed モジュールで取得したい。

URL Builder モジュールにおいて、

  • Base : http://backtweets.com
  • Path elements : search.rss
  • Query parameters :
    • q
    • text : User inputs > URL Input モジュールより流しこむ

Debug : にはテスト用に試したいアドレスを入れておく。

実は最初 Fetch Feed 使うの忘れていて、Fetch Data > Sub Element > Create RSS のモジュールをつなげるという遠回りなことをしていた。 ^^;

 

フィードのアイテムをブロック

使用するモジュール

  • User Inputs > Text Input : 入力
  • コンマ区切りの文字列を正規表現へ
    • String > String Replace : , を $|^ へ変換
    • String > String Regexp : 先頭と末尾に ^   $ をつける。
  • Operators > Filter : 上記の正規表現を元にアイテムをブロック

取得したフィードの item.auther によりつぶやいたユーザ名がわかる。特定のユーザを取り除きたいので、ここでユーザ名を指定してブロックする。ただし、例えば `jutememo,hoge,piyo’ と複数指定されたら、各々のユーザ名に完全に一致するフィードのアイテムをブロック。そのためには次のような手順を踏む。

  1. 入力された文字列の中の `,’ を `$|^’ へ変換
  2. 先頭と末尾に `^’ `$’ をつける
  3. Filter モジュールにおいて、上記で作成した正規表現で一致するアイテムをブロック

 

ここまでで、特定のユーザのつぶやきをブロックしてフィードを表示させることができた。しかし、つぶやいている人の画像は表示されない。できれば フィードのアイテムにある auther 情報を元に、Twitter のプロフィール画像を取得したい。上記で作成したパイプに機能を付け加えていくと、ごちゃごちゃして内容を把握しにくくなるので、関数を定義するようにパイプの内容をできるだけ独立性が高く、長さを短く一画面でおさめる方針で作成していくことにした。

 

Twitter のユーザ情報を取得

パイプ : Twitter.getUser

最初に Twitter のユーザ名を指定したら、プロフィールの画像を表示することを考える。

ユーザ情報を取得するには、Twitter API Wiki / Twitter REST API Method: users show によると、

  • id.  The ID or screen name of a user.
    • Example: http://twitter.com/users/show/12345.json or http://twitter.com/users/show/bob.xml
  • よって、

    http://twitter.com/users/show/ユーザ名.json

    によって JSON形式でユーザ情報を取得できる。

    ユーザ名を与えると、上記の情報を取得するモジュールを作成。

    091016-007

    XML や JSON を取得するには Sources > Fetch Data を利用。

    There's more data on the web than just RSS and Atom feeds. This module can access and extract data from XML and JSON (Javascript Object Notation) data sources on the web.

    (Fetch Data Module より)

     

    JSON の情報からプロフィールの画像を生成

    パイプ : Twitter.getUser.profileImg.toHtml

    次に Twitter.getUser パイプからプロフィールの画像を表示するためにHTML の img タグを生成する。

    091016-008

    JSON 形式で取得したユーザ情報の内 `profile_image_url’ が画像の URL を表わしている。先ほど作成した Twitter.getUser モジュールを My pipes より D&D。Sub-element モジュールを使って JSON の情報を取得する。

    IMG タグでラップするには、Regex モジュールを使い、正規表現 (.*) で全体をグループ化。String Builder モジュールで後方参照し、動的に正規表現を生成する。

     

    IMG タグを DIV タグでラップしてスタイルを適用

    パイプ : Twitter profile image with float left

    先ほどのモジュールと同様な方法で、Twitter.getUser.profileImg.toHtml の結果をラップする。

    091016-009

    できれば、パイプ名からパイプを生成するモジュールがあれば、二つを共通化できるのだけれど。。

     

    つぶやきとプロフィール画像を合わせる

    パイプ : createTwitterPostWithUserImg

    後は、最初に Filter backtweets by twitter user name (No Image) パイプで取得したフィードのアイテム (つぶやき) と、プロフィールの画像を合わせるパイプを作成する。

    091016-010

    あれ?ここも前とほぼ同じだ。

     

    最後に

    パイプ : Filter backtweets by twitter username

    Filter backtweets by twitter user name (No Image)createTwitterPostWithUserImg のパイプをつなぐ。

    091016-011

    Loop モジュール内で、createTwitterPostWithUserImg パイプを呼出している。この呼出しにおいて、item.description を渡しているが、最終的なフィードのアイテムの description は、呼出したパイプの結果と置き換えたいので、Loop モジュールの最後の assgin において、item.description を指定。これで上書きされたことになるようだ。

    ブログへの自分以外のつぶやきを知りたい – Yahoo Pipes を使って BackTweets をフィルタリング

    FriendFeed から Twitter へ投稿

    最近、再び Twitter を使うようになった。同時期、右図の記事を読んだのをきっかけにして FriendFeed もお試し中。

    ブログの更新情報は、FriendFeed を経由して Twitter へ流している。(cf. ブログの更新情報は『Friendfeed』で流すことにした - IDEA*IDEA )

     

    つぶやきやすい Twitter

    Twitter の良い点の一つは、ブログに埋め込まれたコメントと違って、気軽に記事に対してつぶやけること。コメントのように当該の記事の文脈で読まれるのとは異なり、つぶやきは見かけ上つぶやいた人に帰属する形であること。また、ブログへのコメントのように、対人的なコミュニケーションを誘発する可能性が低いことがその敷居を下げている。

     

    つぶやきを知る – ConvoTrack, backtweets

    指定したサイトに関するつぶやきを横に表示してくれる『ConvoTrack』 – 100SHIKI によると、

    ConvoTrackを使えば、指定したサイトに関するTwiiterのつぶやきをそのサイトの横に表示してくれる。

    ConvoTrack には、ブックマークレット (ConvoTrack) も用意されているので、自分のブログに対してどんなつぶやきがあるのか簡単に見ることができる。

    twiStation には、分析系として BackTweets が紹介されていた。こちらはブログのアドレスを入力すると、RSS での取得が可能。

    BackType

    ちなみに ConvoTrackBackTweetsBackType というサービスを利用しているようだ。

    BackTypeは、Disqus、JS-Kit、IntenseDebate、coCommentなど他のコメント・ツールとは異なり、広い範囲のブログプラットフォームとSNS(Twitter、FriendFeedなど)から、ありとあらゆるテーマについてのコメントを収集してくるワンストップサービスだ。

    (コメントの横断的モニタ・ツールのBackType、新機能を追加、資金調達にも成功 より)

    こちらも Connect — BackType に ブックマークレット (Connect Bookmarklet) がある。

     

    自分のつぶやきを取り除くパイプ

    ところで、RSS を取得可能な BackTweetsConvoTrack と同じように、FriendFeed 経由で自動化している自分のつぶやきが表示されてしまう。Advanced Search のオプションもあるけれど、特定の人のつぶやきを抽出できても、指定した人を除くということができないようだ。

    自分へのつぶやきは読む必要がないので、Yahoo Pipes を使って、BackTweets で取得した RSS から、特定のユーザのつぶやきを取り除くことにした。

     

    作成したパイプはこちら →  Pipes: Filter backtweets by twitter username

    使い方
    1. `URL’ に自分のブログのアドレスを入力
    2. 「除きたい Twitter のユーザ名」に自分の Twitter のユーザ名を入力。複数のユーザがある場合はコンマ区切りで入力(空白は入れないこと)
    3. Run Pipe ボタンを押す。

     

    つぶやきの即時性

    つぶやかれたものをすぐに知りたいということに関しては、Topsy の方が反応が良いようだ。(cf. Twitter検索エンジン、Topsyがローンチ―重要性の尺度はTwitterでReTweetされた回数) Topsy では、つぶやかれたものがすぐに表示される。

    Google のドメイン検索と同じように、

    site:ドメイン名

    で検索することにより、自分のサイトのつぶやきを見ることができる。

     

    関連記事

    2009年10月14日水曜日

    Haskell の State モナド (1) - 状態を模倣する

    昔、モナドがよくわからなかったので、さまよっていたら、

    … ネットで見たMonadの説明で一番私がわかりやすいと思ったのは、Wikibooksの説明。Hello World!がブラックボックスな人は、是非一読を。

    (404 Blog Not Found:Haskellで一番難しいのは より)

    最初にこの Wikibooksの説明 を読んだのは去年の 11 月頃。そのときの文書のバージョンは  05:13, 27 October 2008 で、今は内容が随分増えている。前の文書は、現在の Haskell/Understanding monads/State に相当するようだ。

    ところで、上記の解説を最初読んだとき全く意味がわからなかった。 (@_@;) 「3 Stateful Computations」 では、「ランダムな数字を生成する関数」を例に挙げてモナドの説明がされていたけれど、何が言いたいのかさっぱり意図が汲めず。 (+_+) そもそも「状態」というのは何の状態で、それがどうなることなのか?ランダムな数字を生成することと「状態」がどう関係するのチンプンカンプン。

     

    状態の変更

    Stateful Computation ということで、文字通り「状態のある計算」。

    「状態」とは、例えば、Python で変数の状態を変更するとしたら、

    a = 0
    a = 1
    print a

    変数 a の内容を変えるのは全く問題がない。

    しかし、Haskell において、同じノリで、

    a = 0
    a = 1
    main = print a

    と書くと以下のように怒られる。 (+_+)

    Multiple declarations of `Main.a'

    なぜ関数プログラミングは重要か によると、

    関数プログラムは代入文を含まない。それゆえ、変数は一度 値を与えられたら変更されない。もっと一般的ないいかたをすれば、関数プロ グラムには全く副作用がない。関数の呼出しは、結果を計算する以外の作用は もたない。このことは、バグの大きな源のひとつを断つ。

    A Gentle Introduction to Haskell: Functions には、

    Haskell は伝統的な言語のように代入 ( assignment ) ではなく、定義 ( definition ) を用いて計算をすすめます。

    091012-003つまり、`=’ は代入ではなく定義。先ほどの Haskell のコードは、a を 2 回定義したことになるのでエラーが表示された。a という名前の入れ物があり、その中身を入れ替えることはできない。 a はそれが指し示するものと分かち難く結びついている。 Python の場合、変数 a を外から眺めれば、a の中身が文の実行によって変わる。つまり、a の「状態」が変化していると見なすことができる。

    091012-004「状態の変化」と言った場合、上記のように変数の値が再代入によって変わっていくことが一番シンプルな例。しかし、最初に Stateful Computation の「状態」と聞いたとき、反射的に「オブジェクトの内部状態」を連想してしまった。そのため、上記 Wikibooks の解説において「どれがオブジェクトに相当して、何が内部状態に当たるのだろう ???」と混乱。 (+_+) アナロジーによる理解に頼り過ぎると、その枠で固定した視点ができるので理解の阻害要因になることも。かと言って、自分の知識の枠外で想像することはできないので、「オブジェクトの内部状態」を手がかりに、自分なりに Stateful Computation について考えることに。

    ※ 今回は Control.Monad.State は使わずに自前で実装する。

     

    スタックにおける状態の変化

    Haskell で内部状態を持つオブジェクトの動作を模倣するには、値に関数を適用したら、更新した状態の値を新たに作り返すようにする。 (cf. 素朴にエラトステネスのふるい (3) )

    Abstract data type – HaskellWiki2.2 Stack にはスタックの実装例がある。これを参考に必要最小限の関数を持つスタックを書いてみる。

    stack.hs

    pop 関数は、Stack a 型に適用するとタプルが返される。タプルの中身は、最初の要素がスタックから pop により取り出した要素で、2 番目は引数に指定した Stack a 型が、pop により要素が一つ減った状態で返される。実装を見ると、実際は新しくスタックを作って返しているのがわかるけれど、引数と返される値だけを見るならば、この関数によって状態が変更されたと見なすことができる。値を更新できない関数型言語による状態の変更の模倣なので、これを状態の変更だと見なすかどうかは解釈の問題。

     

    スタックの二つの要素を足し合わせる関数

    では、状態の変更であると見なしたとして、 pop 関数とモナドはどう関係するのだろうか?ここからは Wikibooks05:13, 27 October 2008 の説明に沿いつつ、スタックでの場合を考えてみる。

    最初に先ほど定義したスタックを別のモジュールから使う。Stack.hs と同じ階層にファイルを新しく作成し次のように記述。

    import Stack

    これで 変数 s を出力させると以下の通り。

    Stack [5,4,3,2,1]

    (以後この変数 s を使用する。)

     

    sumTwoElem 関数

    このスタック s に対し、「二つ要素を取り出して、足し合わせる関数」 sumTwoElem を定義する。ただし、スタックの要素は加算可能な Num クラスのインスタンス。

    090923-009

    sumTwoElem :: Num a => Stack a -> a
    sumTwoElem stack = (fst $ pop stack) + (fst $ pop stack)

    …としてはダメ。 (+_+) これでは同じ状態のスタックから pop して要素を取り出しているので、 5 + 5 => 10 という結果に。 pop 関数を一度適用した後のスタックから pop しなければならない。

    let 式を使って sumTwoElem を書き直す。 

    sumTwoElem :: Num a => Stack a -> a
    sumTwoElem stack = let (x1, stack') = pop stack
                           (x2, _) = pop stack'
                       in x1 + x2

    手続き指向的なたとえで言うなら、let 式は計算の結果を一時的に蓄え、後の計算で利用するためのもの。pop stack の結果に変数を束縛し、それを 2 回目の pop 関数の適用において参照。let 式は計算の依存関係を作り出しているが、それはつまり計算の前後関係を意味している。

     

    次に上記 sumTwoElem 関数を、pop 関数のように、結果を返したときのスタックの状態も同時に返すように実装にする。

    sumTwoElem :: Num a => Stack a -> (a, Stack a)
    sumTwoElem stack0 = let (x1, stack1) = pop stack0
                            (x2, stack2) = pop stack1
                        in  (x1+x2, stack2)

    スタック s に適用すると結果は、

    (9,Stack [3,2,1])

     

    関数の抽象化

    さて、ここからが混乱の元であったのと同時に興味深いところ。

    Of course, we are Haskell programmers: if there are common patterns or boilerplate in our code, we should search for a way to abstract and capture them in a higher order function.

    (Haskell/Understanding monads - Wikibooks, collection of open-content textbooks より)

    はじめこれを読んだとき、「これ以上どこをどう抽象化すればええっちゅ~ねん」 (@_@;) と思ったけれど。

     

    sumFourElem 関数

    ここで、先ほどの sumTwoElem 関数に戻り、「4 回 要素を pop し、足し合わせる関数」に変更する。

    091013-005

    sumFourElem :: Num a => Stack a -> (a, Stack a)
    sumFourElem stack0 = let (x1, stack1) = pop stack0
                             (x2, stack2) = pop stack1
                             (x3, stack3) = pop stack2
                             (x4, stack4) = pop stack3
                         in  (x1+x2+x3+x4, stack4)

     

    抽象化、一般化

    関数の抽象化とは、具体的なものを一般的なもので置きかえ、その結果、一般的なものから具体的なものを導き出せる状態にすること。その意味からすると、真っ先に上記の pop という具体的な関数を、関数の引数として渡せるように変更することが思い浮かぶ。

    sumFourElem' :: Num a => (Stack a -> (a, Stack a))
                 -> Stack a 
                 -> (a, Stack a)
    sumFourElem' f  = \stack0 ->
                      let (x1, stack1) = f stack0
                          (x2, stack2) = f stack1
                          (x3, stack3) = f stack2
                          (x4, stack4) = f stack3
                      in  (x1+x2+x3+x4, stack4)

    (変更のついでに、第 2 引数 stack をラムダ抽象の引数にした。)

    じぃ~と見ていると (@_@)、繰り返し表われるパターンがなんとなく見えてくる。

    まず、最初に目がいくのは、

    (x, stack○’) = f stack○

    という形の羅列。上から下へと全く同じような格好の文字が並ぶ。

    関数を適用する流れに目をやると、各々の f の前後関係に一定のパターンがあることに気がつく。下図の赤の矢印に注目すると、関数 f が適用する対象は、最初の stack0 を除けば、直前の関数 f の適用の結果、状態が変化したスタック。(もちろん let 式なので、上から下へと順番に並べているのは便宜的なもの。)

    090923-001

    これに対して、どのように抽象化すればいいのだろう?

    おもしろいと思ったのは、先ほどのように適用する関数の一般化を考えるのではなく、関数の適用の流れに注目するところ。関数の適用を繰り返すのを見て、二つの関数のつなげ方を部品として取り出そうとするその発想。

     

    2 つのものをつなぐこと

    ところで、なぜ二つの間のつなげ方を考えるのか? 3つ、4つではなくてなぜ二つなのか?それは、二つのもののつなげ方が決まっていれば、同じものをつなげる限り、下図のように同様な方法でどんどんつなげていけることが理由。当り前のことだけれど、つなぐということに関しては、二つの間のつなげ方がプリミティブであるということ。

    090923-002

    続けて上図のたとえを使うなら、個々の要素を関数に見立て、左からの入力が右へと出力される装置であるとイメージする。同じものを 2つ 3つとつなげた場合、1 つのときと比べて入力から出力までの距離は長くなるが、その入口と出口の形は同じ。要素をつなぐ関数の型を考えるときに、このメタファーを思い出すこと。同じものをつなげられるようにするには、つなぎがどのようであれば良いのか?

     

    ここまでのコード

    mainbak00.hs

     

    二つの関数をつなぐ関数

    では、上記の関数 f のような型の関数を二つつなげる関数を考える。

    ただし、最初は先ほどの sumFourElem’ 関数の青色の点線部分である x1 ~ x4 の扱いについては考えない。ここでは便宜的に、最後に適用された関数の結果を返すように実装しておく。

    Stack a -> (a, Stack a) である型の関数をつなげる関数 comb は以下の通り。 (cf. Meet the Monads。この関数が Monad の >>= に相当。) 実装するときに思い起すことは、先ほど 4 回 pop を繰り返した sumFourElem を書いたとき、各々の pop は何に対して適用したのか?ということ。直前に適用された関数の結果に対して、次に続く関数は適用した。

    comb :: (Stack a -> (a, Stack a)) 
         -> (Stack a -> (a, Stack a))
         ->  Stack a -> (a, Stack a)
    comb m n = \stack0 ->
               let (_, stack1) = m stack0
                   (x, stack2) = n stack1
               in  (x, stack2)

    comb 関数の引数 m, n は、sumFourElem’ 関数において、スタックに連続して適用する二つの関数に相当する。スタックへの適用は m が n に先行すると想定。よって、stack0 に m を適用した結果である stack1 に対して関数 n を適用している。今回は、返り値のタプルの第 1 要素はどうでもよく、第 2 要素が関数 n を stack 1 に適用した結果であることが重要。

    ちなみに、この comb 関数をはじめて見たとき、返す関数の型に疑問を持った。 pop のような関数をつなぎたいのだから、第1引数と第 2 引数が

    Stack a -> (a, Stack a)

    であることは理解できる。しかし、なぜ、つないだ結果がまた同じ型になると考えるのか?それはそうなるように意図して作成した関数だからなのだけれど、であるなら、「なぜそうなるように型を決めたのか?」ということが引っかかった。これは当初先ほど示したメタファーを頭の中に描いていなかったことによる。

     

    comb 関数を使う

    では、comb 関数を使って pop を 2 つつなげ、それをスタック s に適用してみる。

    (comb pop pop) s   -- (4,Stack [3,2,1])

    3 つつなげると、

    (comb (comb pop pop) pop) s   -- (3,Stack [2,1])

    これ以上つなげると括弧で読みにくくなるので、関数を二項演算子のように扱う。

    pop `comb` pop `comb` pop $ s

    これで、comb 関数によってつなげられた関数内において、関数に適用されたスタック s が、あたかも「状態」を保っているかのように連続して関数が適用されることになった。

    このメリットは、comb 関数という「つなぎ」を担当する関数を部品化しておくことにより、関数をつなげるとき、一々個々のつなげ方の詳細を書かなくて済むようになること。先ほど述べたように、2つの間のつなげ方を定めておけば、同じものをつなぐ限りどれだけつないでも同じ「つなぎ」を使えば済む。

     

    ここまでのコード

    mainbak01_00.hs

     

    つなぎを変更

    ここでスタックの「二つ要素を取り出して、足し合わせる関数」の問題に戻る。今定義した comb を少し変更。つなぎの部分で要素を足し合わせてみる。

    comb :: Num a =>
            (Stack a -> (a, Stack a)) 
         -> (Stack a -> (a, Stack a))
         ->  Stack a -> (a, Stack a)
    comb m n = \stack0 ->
               let (x1, stack1) = m stack0
                   (x2, stack2) = n stack1
               in  (x1+x2, stack2)

    このつなぎを使って、

    pop `comb` pop $ s -- (9,Stack [3,2,1])

    3 つつなげるなら、

    pop `comb` pop `comb` pop $ s -- (12,Stack [2,1])

     

    加算を一般化

    しかし、これでは要素を足し合わせることしかできない。つなぎの部分で加算に固定されている。これを一般化してみる。

    comb :: (Stack a -> (a, Stack a)) 
         -> (Stack a -> (a, Stack a))
         -> (a -> a -> a)
         ->  Stack a -> (a, Stack a)
    comb m n f = \stack0 ->
               let (x1, stack1) = m stack0
                   (x2, stack2) = n stack1
               in  (f x1 x2, stack2)

    加算から関数の適用へと変更したので、制約 Num a が必要がなくなった。これを使い、

    (pop `comb` pop) (+) $ s -- (9,Stack [3,2,1])

    今度は 3 つつなげ、先ほどではできなかった計算を試してみる。

    ((pop `comb` pop) (+) `comb` pop) (*) $ s -- (27,Stack [2,1])

    関数を引数に指定できるようになり、先ほどより柔軟性が増したと言える。

     

    ここまでのコード

    mainbak01_01.hs

     
    できない計算

    しかし、問題はつながれた関数同士の結び付きが強いこと。3 つつなげた場合を考えると、

    (5 + 4) * 3 => 27

    渡した二項演算子 +, * は隣り合った要素間でしか適用できない。例えば、数字 5, 4, 3 の順で上記 pop 関数でスタックから取り出し、comb 関数でつなげる限り以下のような計算を作り出せない。

    (5 + 3) * 4

    つまり、つなぎの部分に関数を渡すということは、つなぎに固定されるという点で柔軟性がない。これを克服するために、関数をつなげた後、要素を取り出す pop 関数だけ計算を先に行い、その結果を取りまとめて要素を足し合わせるような計算を後回しにできないだろうか?

     

    クロージャ

    ここで思い出すのが「カリー化」の話。

    例えば、引数を加算する関数 add 。

    add x y = x + y

    これは次のように書ける。

    add = \x y -> x + y

    また次のようにも。

    add = \x -> \y -> x + y

    関数 add に引数を一つ渡すと、引数を一つ取る関数が返される。例えば、値 5 に add を適用したとき、次のようなイメージができる。

    add  = 5 –> \y –> 5 + y

    add 5 と適用したときの引数 `5’ は、返される関数の中に埋め込まれる。返される関数 \y –> 5 + y の立場で考えてみれば、`5’ は自分の伺い知ることのできる範疇の外からやってくる値。で、この値がポイント。

    上記の関数を見ると、無名関数の中に無名関数が閉じ込められている。引数 x は内側の関数から参照できる変数。add 5 と適用した時点で、内側の関数の x は決定する。

    もう少し複雑な例で確認しておく。

    hoge = \x ->
           let a = x + 10
           in \y ->
               x + y + a

    内側の関数は、外側の値を参照できている。

    念のためもっとネストさせてみると、

    piyo = \x ->
           let a = x + 10
           in \y ->
               let b = x + y + 100
               in \z ->
                   x + y + z + a + b

    これもちゃんと動作する。

     

    自由変数

    Closure – HaskellWiki によると、

    A closure, the opposite of a combinator, is a function that makes use of free variables in its definition. It 'closes' around some portion of its environment. for example

    f x = (\y -> x + y)

    f returns a closure, because the variable x, which is bound outside of the lambda abstraction is used inside its definition.

    上記 free variables とは、

    プログラミングにおいては、自由変数とは関数の中で参照されるローカル変数引数以外の変数を意味する。

    (自由変数と束縛変数 – Wikipedia より)

    上記の関数 f の右辺の無名関数内の変数 x は、無名関数内において参照されるローカル変数でも引数でもない変数。よって、その無名関数は自由変数を含むのでクロージャ。先ほどポイントである「値」と言ったのは、「環境」と呼ばれる。

     

    関数を適用する関数

    上記を頭の隅に置きつつ、先ほどの計算に戻る。

    (5 + 4) * 3

    これを分解するための apply 関数を導入。定義は以下の通り。

    apply :: a -> (a -> a) -> a
    apply x f = f x

    第1引数に渡された値に対して、第2引数で与えた関数を適用するという内容。 (これは Monad の >>= の型を真似て作った。)

    普通、関数と言ったら、直接関数を値に適用する。 apply 関数の引数で言えば、第 2 引数の関数を直接第1引数の値に適用。図示するなら、下図のように青色の部分が関数で、黄色が値とイメージできる。

    091011-002

    これに対して、apply 関数は、関数と値との間を取り持つ。

    091011-003

    (5 + 4) * 3 を apply 関数を使って表現するなら、

    5 `apply` (+ 4) `apply` (* 3)

    これを見ると、apply 関数が計算を構成するつなぎの役割をしている雰囲気が伝わってくる。先ほどの図のように書くなら、

    091012-006

    次に、セクションの部分を無名関数に置き換えると、

    5 `apply` (\x -> x + 4) `apply` (\x -> x * 3)

    `apply` の左側の値が右側に投入されている感じが明確になる。

    しかし、このままでは相変らず `apply` のつなぎ方に全体の計算が固定されてしまっている。左側の値は常に真横の右隣の中で使われるだけ。

     

    クロージャで外側の変数を参照

    そこで先ほどのクロージャによる方法で書き直す。

    5 `apply` (\x -> 4 `apply` (\y -> 3 `apply` (\z -> (x + y) * z))) 

    下図は、無名関数を点線で表現し、その中にあるものが内部関数であると想定。内部関数はその外側の関数の値を参照できる。

    091012-005

     

    ところで、無名関数は「できるだけ右へ拡張される」ように解析されるので、括弧を省略できる。

    5 `apply` \x -> 4 `apply` \y -> 3 `apply` \z -> (x + y) * z 

    複数行に渡って書いてみるなら、

    5 `apply` 
         \x -> 4 `apply` 
               \y -> 3 `apply` 
                     \z -> (x + y) * z 

    apply の定義に戻って、具体的に何が行われるのかを見ていくと、最初の値 `5’ に適用すると、その定義より、続く無名関数の引数として適用されることになる。その様子を書くと、

    4 `apply` 
       \y -> 3 `apply` 
             \z -> (5 + y) * z 

    続いて `4’

    3 `apply` 
         \z -> (5 + 4) * z 

    更に `3’

    (5 + 4) * 3 

    となる。

    関数の内側へ内側へと引数を渡していき、最内の関数において全ての引数を参照して計算を組立てるというイメージ。

    更に戻って、先ほど不可能だった以下の計算、

    (5 + 3) * 4

    を構成したいなら、

    5 `apply` 
         \x -> 4 `apply` 
               \y -> 3 `apply` 
                     \z -> (x + z) * y

    ポイントは、内側の関数から外側の関数にアクセスできるので、好き勝手できること。

     

    つなぎでクロージャ

    apply :: a -> (a -> a) –> a を真似して comb 関数を変更する。

    comb ::       (Stack a -> (a, Stack a)) 
         -> (a -> (Stack a -> (a, Stack a)))
         ->        Stack a -> (a, Stack a)
    comb m n = \stack0 ->
               let (x1, stack1) = m stack0
                   (x2, stack2) = n x1 stack1
               in  (x2, stack2)

    ここで 関数 n の型は、前の comb 関数の n とは型が違うことに注意。

     

    とりあえず、最初は使えるか試してみる。型だけ合わせることを考えて…

    pop `comb` (\x1 -> pop) $ s -- (4,Stack [3,2,1])

    当然ながらエラーはでないけれど、pop が 2 回適用されただけ。

    では、徐々に変更してみる。pop した要素を `comb` の右側の無名関数に渡し…

    pop `comb` (\x1 -> pop

    無名関数の中で無名関数を記述する。その際、pop して要素を `comb` で右側の無名関数に渡し…

    pop `comb` (\x1 -> pop `comb` (\x2 –>

    最後は、返される型 Stack a -> (a, Stack a) に合わせると同時に外側の変数を参照して計算を組立てる。

    pop `comb` (\x1 -> pop `comb` (\x2 -> (\stack -> (x1 + x2, stack)))) $ s

    実行すると結果は、

    (9,Stack [3,2,1])

    括弧を省略するなら、

    pop `comb` (\x1 -> pop `comb` \x2 -> \stack -> (x1 + x2, stack)) $ s

     

    ret 関数

    ところで、最後は型を合わせるために Stack a -> (a, Stack a)  に値を投入した。これを毎回書くのは面倒なので、stack の内容は変化させない ret 関数を導入。(Monad クラスの return に相当。)

    ret :: a -> (Stack a -> (a, Stack a))
    ret x = \stack -> (x, stack)

    書き直すと、

    pop `comb` (\x1 -> pop `comb` \x2 -> ret(x1 + x2)) $ s

    先ほど構成できなかった計算を複数行で書くなら、

    print $ pop `comb` (\x1 -> 
            pop `comb`  \x2 -> 
            pop `comb`  \x3 -> 
            ret $ (x1 + x3) * x2) $ s

     

    ここまでのコード

    mainbak01.hs

    module Mainbak01 where
    import Stack
     
    comb :: (Stack a -> (a, Stack a))
         -> (a -> (Stack a -> (a, Stack a)))
         -> Stack a -> (a, Stack a)
    comb m n = \stack0 ->
               let (x1, stack1) = m stack0
                   (x2, stack2) = n x1 stack1
               in (x2, stack2)
     
    ret :: a -> (Stack a -> (a, Stack a))
    ret x = \stack -> (x, stack)
     
    comb_ :: (Stack a -> (a, Stack a))
          -> (Stack a -> (a, Stack a))
          -> Stack a -> (a, Stack a)
    comb_ m n = m `comb` (\_ -> n)
     
    main = do
      print $ pop `comb` (\x1 -> pop) $ s
     
      print $ pop `comb` (\x1 -> pop `comb` \x2 -> ret(x1 + x2)) $ s
     
      print $ pop `comb` (\x1 ->
              pop `comb` \x2 ->
              pop `comb` \x3 ->
              ret $ (x1 + x3) * x2) $ s
    view raw This Gist brought to you by GitHub.

     

    関数の型に別名を付ける

    それにしても、comb 関数の型が長い。 (+_+)

    comb ::       (Stack a -> (a, Stack a)) 
         -> (a -> (Stack a -> (a, Stack a)))
         ->        Stack a -> (a, Stack a)

    これを短くしたいなら、型に別名を付ければ良い。スタックの操作に関する関数なので、「スタック操作型」という意味で次のような名前を付けた。関数も型を持つので、type で別名を付けることができる。

    type StackOp a = Stack a -> (a, Stack a)

    comb を書き直すと、

    comb :: StackOp a -> (a -> StackOp a) -> StackOp a
    comb m n = \stack0 ->
               let (x1, stack1) = m stack0
                   (x2, stack2) = n x1 stack1
               in  (x2, stack2)

     

    第1引数の結果に関心がない場合

    comb 関数は、第 1 引数の計算の結果を受けて、次の関数の計算に進む。つなげる関数によっては結果を必要としないので、comb 関数を使い comb_ 関数を定義。 ( >> に相当。)

    comb_ :: StackOp a -> StackOp a -> StackOp a
    comb_ m n = m `comb` (\_ -> n)

    渡される値を`_’ で無視。それに伴い第 2 引数の型が変わる。

     

    型は十分か?

    ところで、先ほど付けた関数の別名、

    type StackOp a = Stack a -> (a, Stack a)

    型は本当にこれで十分なのだろうか?

    … と言うのは、スタックに適用する関数で、例えば、

    「スタックから pop した要素が、特定の基準を満しているかどうか?」

    を調べる関数 topis を想定して考える。

    第1引数が「特定の基準」を表わす述語、第 2 引数が適用対象のスタック。

    topis :: (a -> Bool) -> Stack a -> (Bool, Stack a)
    topis p s = let (a, s') = pop s
                in  (p a, s')

    これを comb 関数を使って、

    「連続して pop した要素が、全て特定の基準を満しているかどうか」

    を判断できる関数を作ってみる。topis を comb に適用するためには、第 1 引数に関数を与えてから渡す。

    print $ topis (> 4) `comb` (\x1 -> 
            topis (> 3) `comb`  \x2 ->
            topis (> 2) `comb`  \x3 ->
            ret $ and [x1,x2,x3]) $ s

    これを実行してみると結果は… (@_@;)

    Couldn't match expected type `Bool' against inferred type `Integer'
          Expected type: Stack Bool
          Inferred type: Stack Integer

    このエラーは、スタックの要素が数字であるのに対して、上記の関数の結果のタプルの第 1 要素が真偽値のため。これを解決するためには、スタックの要素の型と、スタックに対して操作する関数が返す型 (タプルの第 1 要素) が異なってもいいように、次のように別名を修正しなければならない。

    type StackOp a b = Stack a -> (b, Stack a)

    これに伴い、comb, comb_, ret 関数の型を変更する。 ( cf. 余談 (失敗) )

    comb :: StackOp a b -> (b -> StackOp a c) -> StackOp a c
    comb m n = \stack0 ->
               let (x1, stack1) = m stack0
                   (x2, stack2) = n x1 stack1
               in  (x2, stack2)
    
    comb_ :: StackOp a b -> StackOp a c -> StackOp a c
    comb_ m n = m `comb` (\_ -> n)
    
    ret :: b -> StackOp a b
    ret x = \stack -> (x, stack)

    これで上記 topis 関数を使った関数を実行できるようになった。

     

    push, empty もつなげられるように変更

    ところで、Stack モジュールの push 関数は次のような定義だった。

    push :: a -> Stack a -> Stack a
    push x (Stack xs) = Stack (x:xs)

    これに対して、comb 関数の型は、

    comb :: StackOp a b -> (b -> StackOp a c) -> StackOp a c

    別名の定義は、

    type StackOp a b = Stack a -> (b, Stack a)

    上記の型に合わせるには push 関数を変更する必要がある。

    push' :: a -> Stack a -> ((), Stack a)
    push' x (Stack xs) = ((), Stack (x:xs))

    これで push’ 関数の第 1 引数に値を適用すると、StackOp a b 型の値を受け入れる関数に渡せるようになった。

    例えば、「適用するスタックを変化させない」関数を定義してみる。

    091014-006

    print $ pop `comb` push' $ s
    print $ push' 10 `comb_` pop $ s

    結果は、

    ((),Stack [5,4,3,2,1])
    (10,Stack [5,4,3,2,1])

    連続して push するなら、

    print $ push' 10 `comb_` push' 9 `comb_` push' 8 $ s
    結果は、
    ((),Stack [8,9,10,5,4,3,2,1])

     

    empty 関数も同じように StackOp a b 型に合うように変更してみる。

    empty' :: Stack a -> ((), Stack a)
    empty' _ = ((), Stack [])

    これを使って、

    print $ empty' $ s
    print $ push' 10 `comb_` empty' `comb_` push' 0 `comb_` push' 1 $ s

    結果は、

    ((),Stack [])
    ((),Stack [1,0])

     

    ここまでのコード

    stack.hs

    mainbak03.hs

     

    余談 (失敗)

    関数の型に別名を付けたときに、型を間違えて修正したところより。

    (>>=) の型を見誤る

    実は当初、間違えて以下のように関数の型を定義していた。

    comb :: StackOp a b -> (b -> StackOp a b) -> StackOp a b
    comb_ :: StackOp a b -> StackOp a b -> StackOp a b

    これに気がつかず、push’ 関数を使ってエラーが… (@_@;)

        Couldn't match expected type `()' against inferred type `Integer'
          Expected type: Stack ()
          Inferred type: Stack Integer

    原因に ずーーーーーーっと 気がつかなくてハマった。(+_+)

    最初は 「クラスで型宣言せずに、type を使って別名にしているのがダメなのかなぁ ?」 と思い、newtype 宣言で、

    newtype StackOp a b = StackOp {run :: Stack a -> (b, Stack a)}

    Monad クラスを真似て、

    class MyMonad m where
        comb :: m a -> (a -> m a) -> m a
        ret  :: a -> m a
    
    instance MyMonad (StackOp a) where
        ret x = StackOp $ \stack -> (x, stack)
        m `comb` n = StackOp $ \stack0 ->
                     let (x1, stack1) = run m stack0
                         (x2, stack2) = run (n x1) stack1
                     in  (x2, stack2)

    このときも、まだ (>>=) を真似たつもりの comb 関数の型定義を間違えているのに気がつかず。。(+_+)

    間違えたままで、上記を使った関数を定義。スタックの要素を二つ足し合わせる関数 popop 。

    poppop = StackOp pop `comb` \x1 ->
             StackOp pop `comb` \x2 -> 
             ret $ x1 + x2

    ここでは問題が表面化しない。なぜなら、poppop 関数において扱っているスタックの要素と、ret 関数によって返した結果のタプルの第 1 要素が同じ Integer 型であるため。

    しかし、次にスタックから pop して push する関数 poppush を定義してエラーが発生。

    poppush = StackOp pop `comb` (\x -> StackOp (push' x))
    poppush' = StackOp pop `comb` (StackOp . push')

    エラーの内容は、

        Couldn't match expected type `()' against inferred type `Integer'
          Expected type: Stack ()
          Inferred type: Stack Integer

    このエラーが発生する理由は、Stack モジュールの push’ の型を見直せば、

    push' :: a -> Stack a -> ((), Stack a)

    返ってくる結果のタプルの第 1 要素と、スタックの要素の型が異なるから、comb 関数を push’ に適用できない。

    Monad の (>>=) の型を見直すと、

    (>>=) :: forall a b. m a -> (a -> m b) -> m b

    このときになって、やっと次のように見間違えていたことに気がついた。 パタッ(o_ _)o~†

    (>>=) :: m a –> (a –> m a) –> m a

    よって、MyMonad クラスの comb 関数の型を次のように修正する必要がある。

    comb :: m a -> (a -> m b) -> m b

    これで poppush 関数が定義できた。

     

    クラスで型宣言してオーバーロードする方が型がスッキリ

    次に、(>>) に相当する comb_ 関数も定義してみる。

    comb_ :: StackOp a b -> StackOp a b -> StackOp a b
    comb_ m n = m `comb` (\_ -> n)

    ここでもまた最初、間違えに気がつかなかった。(+_+)

    この関数を用いて「スタックの要素を 2 回 push’ する」関数を定義。

    pushpush = StackOp (push' 10) `comb_` (StackOp (push' 9))

    問題なし。

    次にスタックの要素を pop し、それが特定の基準を満しているかチェックする topis 関数を使って、

    print $ run (StackOp (topis (> 4)) `comb` (\x1 ->
                 StackOp (topis (> 3)) `comb`  \x2 ->
                 StackOp (topis (> 2)) `comb`  \x3 ->
                 ret $ and [x1, x2, x3]))$ s

    これまた問題なし。

    しかし、次に「スタックを pop して push’ する」関数を定義してやっと問題が表面化。

    pushpop = StackOp (push' 10) `comb_` StackOp pop

    エラーがでる理由は、push’ の返すタプルの第 1 要素が () であるのに対して、pop はこの関数が適用されるスタックの要素の型に依存するため、先ほど定義した comb_ 関数の型に合わないから。comb_ 関数の型を次のように修正しなければならない。

    comb_ :: StackOp a b -> StackOp a c -> StackOp a c

     

    クラスで型宣言した方がシンプル

    それにしても、使う型変数が 2 つ以上だと、慣れてないと見落してしまう。 (+_+) これは、comb_ 関数を MyMonad クラスとは独立に定義したために、型の定義が複雑になったことによる。

    MyMonad クラスの型定義において comb_ 関数を追加するなら、

    comb_ :: m a -> m b -> m b

    この関数を StackOp a を MyMonad のインスタンス宣言に追加する。

    m `comb_` n = m `comb` (\_ -> n)

    こちらの方が型変数が少ない分、型の間違えに気づきやすいなぁ~。 ^^

     
    ここまでのコード

    mainbak05.hs

     
    関連記事