特定非営利活動法人MOSA

MOSAはソフトウェア開発者を支援します

  • iPhone/iPod touch アプリ紹介
  • MOSA掲示板
  • 活動履歴
  • About MOSA(English)
  • BIZMATCH
  • メールマガジン好評配信中

MOSA Developer News[MOSADeN=モサ伝]第263号

2007-08-21

目次

  • 「りんご味Ruby」       第9回  藤本 尚邦
  • 藤本裕之のプログラミング夜話   #120
  • 高橋真人の「プログラミング指南」  第118回
  • 書籍紹介            たのしいCocoaプログラミング

りんご味Ruby   第9回  藤本 尚邦

今回は、RubyをRubyたらしめているに違いない、ブロック付きメソッド呼出しについて説明します。いきなりですが、簡単なコード例をいくつか上げてみましょう。

-----------------------------------------------
(1..10).each      { |i| puts i }       # 1から10までプリント
(1..10).map       { |i| i * -1 }       # => -1から-10までの配列
(1..10).find_all  { |i| i%2 == 0 }     # => 2から10までの偶数の配列
(1..10).inject(0) { |sum,i| sum + i }  # => 1から10までの和
(1..10).inject(1) { |prd,i| prd * i }  # => 1から10までの積 (階乗)
-----------------------------------------------


1..10 というのは、「1から10まで」を表わすRangeオブジェクト(リテラル)です。この場合、`{‘と`}’で囲った部分がブロックです。

そういえば、初回で紹介したRSSリーダにも、2つのブロック付きメソッド呼出しが含まれていました。

----------------------------------------------- RSSリーダより抜粋 --
open(url)      { |io| RSS::Parser.parse(io.read, false) }
rss.items.each { |item| puts item.title }
-----------------------------------------------

■ ブロックとは?

そもそもブロックとは何でしょう? ひとことで言うと、実質的には

 名前のない「手続き定義」

です。メソッド呼出しがブロックをともなう場合、そのブロックで定義された「手続き」がメソッドの(意味的な)実引数として渡されます。

■ ブロック付きメソッド呼出しとは?

ブロック付きで呼び出されるメソッド(の定義)には、ブロックとして渡された「手続き」を、いつ・どこで・何回・どんな風に、実行するかがプログラムされています。どんな「手続き」かはまだわからないけど、どんな風に「手続き」を使うかにはパターン(抽象)があるというケースは結構あります。

・処理の繰り返し(イテレータ)
・並べ替え(ソート)での値の比較
・コンピュータシステム上のリソースへのアクセス
 ・IO (ファイル・ソケットなど)
 ・メモリ、スレッドなど
 ・DBトランザクション

Rubyに標準で入っているクラスライブラリには、さまざまなパターンを扱う多くのブロック付きメソッドがあらかじめ定義されています。もちろん、プログラマが自分で見つけた抽象をブロック付きメソッドとして定義することもできます。ブロック付きメソッド呼出しは、この種のパターンを抽象化してプログラムを簡潔で読みやすくするための強力な記法なのです。

■ ブロックの記法

ブロックの記法は以下の2つです。

 do |ブロックの仮引数リスト| ブロックの手続き定義 end
 {  |ブロックの仮引数リスト| ブロックの手続き定義 }

ブロック全体(仮引数リストと手続き定義)を囲む両端の記号が違うだけで、「無名の手続き定義」としての意味は同じです。便宜上、do/endで囲む記法のブロックのことを「doブロック」と呼ぶことにします。

これらの記法をどう使い分けるかですが、実引数リストの場合と同様、好みの問題かもしれません。私はおおざっぱに

・1行で書けるときは { … }
・手続き定義が長い(改行あり)ときは do … end
・返す値を使うときは { … }
・返す値を捨てるときは do … end
・DSLっぽさを強調したければ do … end

という感じで使い分けています。

■ ブロック付きメソッド呼出しの記法

 レシーバ.メソッド名               ブロック
 レシーバ.メソッド名()             ブロック
 レシーバ.メソッド名(実引数リスト) ブロック
 レシーバ.メソッド名 実引数リスト  doブロック

実引数があるときに括弧を省略した場合(4つめ)には、doブロックしか使うことができません。

ブロックのみを評価(eval)すると構文エラーになります。ブロック単体はRubyプログラムではないのです。メソッド呼出しと組み合わせて初めて、Rubyプログラムとして成立します。

■ 実例

標準Cライブラリには qsort という関数があります。この関数は、配列(とい
うかメモリアドレスとそのデータ構造に関する情報)および比較関数を引数に
とり、その配列を比較関数を使ってクイックソートするというものです。

--------------------------------------------- C言語の qsort 使用例 --
#include 
#include 
static int comp_func(const void* a, const void* b) {
 return *(long*)a - *(long*)b;
}
static void ary_fill_randam(long* ary, size_t len) {
 while (len-- > 0) ary[len] = random() % 1000;
}
static void ary_print(long* ary, size_t len) {
 int i;
 for (i = 0; i < len; i++) printf("%ld n", ary[i]);
}
int main() {
 long ary[100];
 ary_fill_randam(ary, 100);
 qsort(ary, 100, sizeof(long), comp_func);
 ary_print(ary, 100);
 return 0;
}
---------------------------------------------


これは

・配列に乱数をセットして
・それを昇順にソートして
・配列の内容をプリントする

というqsortを使ったCプログラムです。同じようなプログラムをRubyで書くと

--------------------------------------------- Rubyの場合 --
ary = (1..100).map{ rand(1000) }
ary.sort! {|a,b| a - b }
ary.each { |i| puts i }
---------------------------------------------


となります。上記の3つのプロセスすべてについて、あらかじめ定義されているブロック付きメソッドを使って簡潔に書くことが出来ました。ループ回数を制御するためだけの変数(Cプログラムのiやlen)がまったくないのも、繰り返しが抽象化されていることによる効果です。

■ まとめ

繰り返し抽象に関するブロック付きメソッドにはどんなものがあるのかな、ということに興味を持った方はRubyのオンラインマニュアルの

 http://www.ruby-lang.org/ja/man/?cmd=view;name=Enumerable

あたりを見てみるのもよいかもしれません。

今回は、概略と繰り返し抽象の簡単な説明だけで終ってしまいました。次回は、ブロック付きメソッド呼出しのもっとRubyらしい実例を紹介できればと思います。

[補足] DSL(Domain Specific Language)について詳しくは、前回紹介したマーチン・ファウラ氏の記事や、Wikipediaの記事:

* http://capsctrl.que.jp/kdmsnr/wiki/bliki/?DomainSpecificLanguage
* http://ja.wikipedia.org/wiki/ドメイン固有言語

をご覧ください。

藤本裕之のプログラミング夜話 #120

 承前のごとく、タカハシ編集長のリクエストで他のnibファイル(TabView.nib)から読み込んだビュー上にあるボタンのEnable/Disableも切り替えるやり方を解説する。これまたいろいろやり方はあるが、ここでは読み込んだビューのコントローラからボタンのインスタンスへのポインタを受け取ってそいつにEnable/Disableのメッセージを送れるようにすることにする。このやり方ならEnable/Disableだけでなくボタンに表示している文字列を変更するとか、そういうことも簡単にできるからね。

 まずはMyApplication.hを開いて、以下のインスタンス変数を定義する。

IBOutlet NSButton*            button1Sw;
IBOutlet NSButton*            button2Sw;
IBOutlet NSButton*            button3Sw;


 これらはちゃんと目当てのボタンがEnable/Disableできるかどうかを確かめるためのスイッチである。チェックボックスとしてタブビューの下に配置することにする。え、別にこれをインスタンス変数として定義する必要はないんぢゃないかって? だって、TabView.nibから読み込むビューが表示されていない時にはこいつらの方をDisableできないと変でしょ?

 お次はこれらのチェックボックスが押されたときに実行されるメソッドを定義。

- (IBAction)toggleButton1:(id)sender;
- (IBAction)toggleButton2:(id)sender;
- (IBAction)toggleButton3:(id)sender;


 こんだけを付け加えたらInterface BuilderでMainMenu.nibを開き、Instancesのタブにこのヘッダファイルをドラッグ&ドロップする。タブビューの下にチェックボックスを3つ、名前は「Button1」「Button2」「Button3」でいいや、こんなところで凝っても何も出ないからな。で、こいつらに上のアウトレット(「button1Sw」など)とアクションメソッド(「toggleButton1」など)を連結する。あとはコーディングだけ。

 MyApplication.mを開き、-tabView:didSelectTabViewItem: を以下のように書き換える。

-(void)tabView:(NSTabView*)tabView didSelectTabViewItem:(NSTabViewItem*)tabViewItem {
    NSString* identifier = [tabViewItem identifier];
    BOOL      switches = NO;
    if([identifier isEqualToString:@"2"]){
         if(tabViewController == nil){
              tabViewController = [[MyTabViewController alloc]
                                  initWithWindowNibName: @"TabView"];
              [tabViewController setTarget:self];
              [tabViewItem setView:[[tabViewController window] contentView]];
         }
         switches = YES;
    }
    [button1Sw setEnabled:switches];
    [button2Sw setEnabled:switches];
    [button3Sw setEnabled:switches];
}


 「switches」はさっき作ったチェックボックスどもをEnableにするかDisableにするかのフラッグ。タブビューに表示されるのがTabView.nibのビューのときはYESにしてこれらのチェックボックスをEnableにする。次に「toggleButton1」などを用意……。みんな同じなので以下ではbutton1についてだけ示す。

- (IBAction)toggleButton1:(id)sender {
    [[tabViewController button1] setEnabled:(BOOL)([sender state] == NSOnState)];
}


 ここで「tabViewController」に送っている「button1」などのメッセージがつまりTabView.nibのビュー上にある「buttonNo1」へのポインタを返すもの。MyTabViewController.hを開いて

- (id)button1;

と定義し、MyTabViewController.mに

- (id)button1{
    return buttonNo1;
}


とその中身を書けばできあがりである。ここまでのプロジェクトを編集長に頼んでMOSAのサイトにアップしてもらうので、詳しくはそれを見て確認してちょうだい。質問も歓迎。
                            (2007_08_16)
◇編集部から◇
今回のサンプルプログラムはMOSAのwebサイトからダウンロード可能です。
http://www.mosa.gr.jp/?p=1243

高橋真人の「プログラミング指南」第118回

プログラマのためのオブジェクト指向再入門(26)

〜XcodeによるPowerPlant X入門(9)〜

 こんにちは、高橋真人です。
 さて、今までの説明で少しはPPxのイベント処理の動きが見えてきたでしょうか? 今度は前のような変なやり方ではなく、ちゃんとWindow自身に自分へのイベントを処理させるようにしてみましょう。
 今回は、いきなりコーディングを始めるのでなく、まずこの処理をするための戦略を考えてみます。

 まず、前回のコードでは、HICommandのCarbonイベント、つまり、kEventClassCommand/kEventCommandProcessの組み合わせ(以下、便宜的に「コマンドイベント」と記します)でCarbonイベントがアプリケーション(PPx::Application)に対して送られた場合に、それを処理する形でした。
 それを今度は、イベントがApplicationに送られる前に、もしアクティブなウインドウ(PPx::Windowまたはそのサブクラス)があったら、先にそっちに
イベントが渡るようにしたいというわけです。
 ただこの場合、問題はすべてのコマンドはコマンドイベントの付加情報として渡ってくるということです。それがなぜ問題なのかというと、PPxの振り分け機能が「イベントクラス/イベント種類」の組み合わせパターンごとに1つの関数を割り振るようになっているからです。

 つまり、HICommandのいろいろなもの、たとえばkHICommandNewもkHICommandOpenも、イベントの組み合わせパターンという観点から見れば何ら違いがないため、PPxのイベント振り分けの仕組みで別々の関数に割り当てることができないのです。
 しかし、ここでやりたいのは、例えばkHICommandNewはPPx::Applicationの関数に処理をさせ、kHICommandCloseはPPx::Windowに処理させるということなのです。
 ちなみに、通常のCarbonのプログラムでは、以下のような手順でこういうケースに対処します。
 まず、ウインドウのイベントハンドラでは、

OSStatus
EventHandlerOfWindow(..., EventRef event, ...)
{
    EventClass theClass = GetEventClass(event);
    EventKind theKind = GetEventKind(event);

    if (theClass == kEventClassHICommand
        && theKind == kEventCommandProcess) {
        HICommand command;
        GetEventParameter(event, kEventParamDirectObject,
                 typeHICommand, NULL, sizeof(command), NULL, &command);

        if (command.commandID == kHICommandClose) {
            // ウインドウのクローズ処理をここで行う
            return noErr;
        }
    }

    return eventNotHandledErr;
}


 こんな感じに自分に関心のあるイベント&コマンドの時にのみnoErrを返し、それ以外の時にはeventNotHandledErrを返すのです。Carbonイベントの仕組みでは、ハンドラがeventNotHandledErrを返した場合は次の処理候補に処理を依頼するようになっています。ここでの例では、次の処理候補はアプリケーションのイベントハンドラということになります。この辺の仕組みは、前回紹介したAppleのデベロッパサイトのドキュメントを参照してください。

 さて、PPxは以上の問題をどのように解決するのでしょうか?
 PPxの主要な機能の一つに、実はこのコマンドの処理があります。PPxでは、Carbonイベントの種類だけではなく、何とCommandの種類ごとに専用の関数を用意できる仕組みがあるのです。早速その仕組みを説明いたしましょう。
 今まで見てきましたように、PPxではEventDoerというイベント処理のためのクラスをカスタマイズ(サブクラス化)することにより、イベントの種類ごとに個別に反応する関数を呼び出す仕組みを実現しています。
 今回の、コマンドごとに個別の処理を行う場合にもこれと同じ仕組みを利用します。PPxでは、独自にイベントクラスを用意し、それにHICommandのIDをイベントの種類とすることで、イベントのクラス/種類の組み合わせを実現しています。
 このために用意されている独自のイベントクラスがSpecificCommandDoerというクラスです。コマンドは多くの場合、ユーザーのメニュー選択に伴って発生するので、このクラス以外にメニューのハンドリングなどを含めた関連クラスがいくつかあり、これらはすべて、PPxCommadEvents.hに記述されています。
 本質的にはSpecificCommandDoerの使い方を理解できれば、他のクラスの使い方に関しても要領はほぼ同じなので、やり方は想像付くと思うのですが、まずはメニュー処理も同時にハンドリングしてくれるSpecificMenuCommandDoerというクラスを取り上げてみます。
 実際にやってみましょう。
 連載の第111回でやったのと同じ要領で今度は、PPxForXcode03というプロジェクトを作ります。01を複製しても02を複製しても構いませんが、01を複製した場合は、MyApplication.hとMyApplication.cpを作っておいてください。
 加えて、今度のプロジェクトではMyWindow.hとMyWindow.cpというファイルを加えます。やり方については、連載の第112回目を参照してください。

以上の操作で、
main.cp
MyApplication.h
MyApplication.cp
MyWindow.h
MyWindow.cp
という5つのソースファイルがプロジェクトに加わっているはずです。ではコードを入れていきます。

まず、main.cpは前のもの(02)と共通です。

====================== MyApplication.h =========================

#include 
#include 

class MyApplication : public PPx::Application,
                     public PPx::CommandConverter,
                     public PPx::SpecificMenuCommandEnableDoer,
                     public PPx::SpecificMenuCommandDoer
{
public:
                    MyApplication();


protected:
    virtual OSStatus    DoSpecificCommand(
                            PPx::CommandIDType,
                            PPx::SysCarbonEvent&       ioEvent);
};

====================== MyApplication.cp =========================

#include "MyApplication.h"
#include 
#include "MyWindow.h"

MyApplication::MyApplication()
{
    EventTargetRef targetRef = GetSysEventTarget();
    CommandConverter::Install(targetRef);
    PPx::SpecificMenuCommandEnableDoer::Install(targetRef);
    PPx::SpecificMenuCommandDoer::Install(targetRef);
}


OSStatus
MyApplication::DoSpecificCommand(
                            PPx::CommandIDType,
                            PPx::SysCarbonEvent& ioEvent)
{
    OSStatus status = eventNotHandledErr;

    MyWindow* theWindow = PPx::NibDecoder::CreateWindowFromNib(
                                                   CFSTR("main"), CFSTR("MainWindow"));
    if (theWindow) {
        ::RepositionWindow(theWindow->GetSysWindow(), nil, kWindowCascadeOnMainScreen);
        theWindow->Show();
        status = noErr;
    }

    return status;
}

====================== MyWindow.h =========================

#include 
#include 

class MyWindow : public PPx::Window,
                 public PPx::SpecificMenuCommandDoer
{
    virtual OSStatus    DoSpecificCommand(
                            PPx::CommandIDType,
                            PPx::SysCarbonEvent&          ioEvent);
private:
    virtual void        FinishInit();
};

====================== MyWindow.cp =========================

#include "MyWindow.h"
#include 

void
MyWindow::FinishInit()
{
    EventTargetRef targetRef = GetSysEventTarget();
    PPx::SpecificMenuCommandDoer::Install(targetRef);
}

OSStatus
MyWindow::DoSpecificCommand(
                PPx::CommandIDType,
                PPx::SysCarbonEvent&   ioEvent)
{
    Close();

    return noErr;
}


 以上を打ち込むかコピーしたら、ビルドして実行してみてください。まずは動作を観察しましょう。前のものと挙動として変わっているのがどこなのかをいろいろ試して探してみてください。

◇MOSAからのお知らせと編集後記は割愛します◇

 

 MOSA Developer News   略称[MOSADeN=モサ伝]
        配信停止 mailto:mosaden-ml@mosa.gr.jp
 記事内容に関するご意見 mailto:mosaden-toukou@mosa.gr.jp
      記事投稿受付 http://www.mosa.gr.jp/?page_id=850
Apple、Mac OSは米国アップル社の登録商標です。またそのほかの各製品名等
はそれぞれ各社の商標ならびに登録商標です。
このメールの再配信、および掲載された記事の無断転載を禁じます。
特定非営利活動法人MOSA  http://www.mosa.gr.jp/
Copyright (C)2007 MOSA. All rights reserved.