Quantcast
Channel: いげ太の日記
Viewing all 26 articles
Browse latest View live

目玉焼きの議論

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

それなりに昔の、他愛のないありふれたストーリーだ。正確にいつだったかなんて忘れてしまった。中学か、高校時分の話である。そのとき教室にあった顔から思い出そうとしても思い出せず、ともかくも、そういう出来事があったということだけが記憶にある。今日はその出来事を、すこしの脚色をもって話そう。

それは、いつものように休み時間をぼんやりと過ごしていたときのことだった。バタバタと足音を立て、特に親しいでもない女子が3人、僕の席に現れた。僕の視界と、緩やかに流れていた時間はさえぎられてしまった。彼女らは、高揚した様子で、点になった僕の目に真剣な眼差しをぶつけてきて、矢継ぎ早にこう問いかけた。

「なあ、目玉焼きってなにかけて食べる?」

沈黙、と呼ぶにはわずかな間をもって、僕は素直にこう答えた。「いや、なんもかけへんけど。そのまま食べる」。驚きと落胆が入り混じったような顔をして、彼女たちは「えー、なにそれー。つまらーん」といって、そして、そう言い終える前にくるり背を向け去っていった。彼女らは、同じ質問のために、次のターゲットを探しているようだった。わけがわからない。僕はといえばまだキョトンとしていて、しかし、気になって教室を見回してみて、そうしてようやくと状況が飲み込めた。

いまや、このクラスは議論と投票のただ中にあったのだ。そして目下、醤油派の東軍とソース(ウスターソース)派の西軍は伯仲し一触即発の状態にあり、両軍とも、自軍に引き入れるためのいわば票集めに奔走している、という戦況である。そう、そしてそんな中、僕は事実上の白票である「なし派」にその一票を投じてしまったのだ。これはつまり、期せずして、どちらの軍にも付かぬという意識表明を行ったということに他ならず、いうなれば、クラスのメインストリームとの断絶だ。

しかして僕は傍観者となった。中立軍といえば聞こえがいいが、これは円グラフでいうところの「その他」にあたる。しかも、もっとも小さいパイである「その他」だ。それは、ノイズと呼ばれるヘタであり、たいていの場合、実のない部分として切り落とされる運命にある。

でも、まあいいか。緩く、時が戻りくるのを、僕は顎肘をついて待っていた。

醤油をかけるか、ソースをかけるか、それが問題だ。ぼんやりとした意識の中に流れ込んでくるコンテキスト。教室はその議論に支配されていた。そうして、それはゆっくりと海岸線を形成していった。塩派や、マヨネーズ派や、なし派を除いた生徒たちの手によって。

塩派の急先鋒が上陸作戦を試みたりもしたが、彼らの主張は主流派の大きな声にあっけなくかき消された。残りの連中はといえば、好むと好まざるとにかかわらず、無意識的にそれを眺めていた。大海に頼りなく浮かぶボートから望む陸地は、どうあれ羨望の対象となりえる。たとえもしそれが不毛の地であったとしても、ボートの上からそれを知る術はない。

僕は、半分の意識をその騒動にやりながら、もう半分の意識で考えていた。奴らときたら、目玉焼きに真っ赤なジャムを塗って、皿ごと食べようとしてるんだ。

正しさを主張している人、暇つぶしがてらに興じる人、そして、主流派に身を置いていたい人。たくさんの人、たくさんの主流派の人が、発言をしたりしなかったりしていた。彼らにとって、それは重要な論争だったのだろう。しかしながら、いつの間に抜け落ちてしまったのか、それともそんなもの元からなかったのか、もはや答えを導き出すことやその過程で生み出される見地などに意味はなく、議論というコミュニティ自体に価値が存在していた。

僕はなにも言わなかった。僕はなにも言う気にはなれなかった。そんなのはどっちだっていい。ただ、言っておきたいのは、ボートで海に揺られてみるってのもそんなに悪くはないってことで、つまり、そう、どうあれ目玉焼きはうまいんだ。みんな目玉焼きが大好きなんだ。


C# 3.0 で拡張メソッドによる Mix-in 的ななにか

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

以下のすばらしい記事を眺めながら。

これはマネしたい。

Mix-in ってなにさ

イコール多重継承でしょ、と思ってたらちょっと違うようで、また、複数インターフェースの実装を指すのでもないようだ(囚人さんの記事が参考になる)。さりとて、duck typing や structural subtyping の話かというと、それはむしろ論点がズレている。混乱してきた。Wikipedia もめくっておこう。

Mixin は、メソッドが実装されたインターフェースとして見ることもできる。

…(中略)…

Java や C# などの一般的な言語では、Mixin の機能の一部は、インタフェースにより提供される。しかし、インタフェースはクラスがサポートしなければならないものを指定するのみで、実装を提供しないため、ポリモーフィズムを提供するためにのみ有用である。インターフェイスに依存し実装を提供するクラスが、共通の振る舞いを一箇所にリファクタリングするために有用である。

インターフェイスをアスペクト指向プログラミングと組み合わせた場合 C# や Java などの言語で完全な Mixin の機能を提供できる。

Mixin - Wikipedia

もう一声、あともうすこし説明が欲しいところ。Ruby が Mix-in を直接サポートしているらしいので、そこからたぐり寄せてみる。次の記事が特に参考になるか。1 ページまるごと重要そうに見えるので引用はしない。

まつもと直伝 プログラミングのオキテ 第3回(3):ITpro

ふむふむ。なんとなくわかったような。つまり、単一継承と多重継承のいいとこ取り、すなわち、継承によるクラス階層をツリー状に保ったまま複数のクラスから実装コードを取り込みたい、その要求を満たすためのテクニックが Mix-in という手法であると。

C# で Mix-in

なるほど。つまり、最初に挙げた一連の記事で言われていることとは、空のインターフェースに拡張メソッド(Extension Methods)を足すとそれって Mix-in クラスだ(module とみなせる)よね、ということだったんだな。

うん。おおよそ理解できてきたような気がするので、コード書いてみることにしよう。

/*
* ある種の実験的なコードです。.NET のネーミング ルールも無視しています。
*/


/// Mix-in クラス(その1)
public interface PrintableMixin {}
public static class PrintableExtentions
{
public static void Print<T>(this PrintableMixin print, T obj)
{
System.Console.WriteLine(obj.ToString());
}
}

/// Mix-in クラス(その2)
public interface DummyDataMixin {}
public static class DummyDataExtentions
{
public static string GetStringValue(this DummyDataMixin dummy)
{
return "Foo";
}

public static int GetIntValue(this DummyDataMixin dummy)
{
return 1;
}
}

/// Mix-in クラスの実装クラス
public class Hoge : PrintableMixin, DummyDataMixin
{
}


class Program
{
static void Main(string[] args)
{
Hoge hoge = new Hoge();
hoge.Print(hoge.GetStringValue());
hoge.Print(hoge.GetIntValue());
}
}

おお。動いた。あまりよい例ではないけど。Mix-in できたっぽい(これがモノホンの Mix-in なのか、擬似でしかないのか、いまいち判断できないので「ぽい」って語尾が外せないというこの悲しさ)。

エクステの可能性

拡張メソッドは既存のクラスにメソッドを付け加える。インスタンス メソッドのように見えて、インスタンス メソッドのように使うことができるが、それは見せかけでしかなく、実際にはそれは別の static クラスに定義された static メソッドでしかない。

だから、本来メソッド実装を持たないものに対しても、インスタンス メソッドのように見えるものを持たせることが可能になる。たとえば、メソッド実装を持つ enum。たとえば、メソッド実装を持つ interface。従来存在し得なかったオブジェクト達が新たなる地平を切り開くのだ。

と、大風呂敷を広げてみたものの、この Mix-in がハマる場面もそう多くはないだろうけど。ただ、知っておいても損はしないというか、なんかお得感があるよね。

(完全に余談)ところで、F# が将来のために reserve しているキーワードの中に mixin て単語が見えて非常に気になるんだけれども、そのうち言語機能として実装されたりするんだろうか。どうなんだ。

関連記事

[C#] ダイアモンドだね

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

Ruby の Mix-in がステキそうなのは動的言語だからなのかな。どうも、C# で有用な Mix-in ってなんだろうって考えると、結局、多重継承みたくなっちゃうんだけども。んー、とりあえず書いてみるべか。

というわけでコード、ダイヤモンド継承。お楽しみに~。

/*
* [EatingMixin]
* △
* │
* ┌───┴───┐
* │ │
* [FoodMixin] [BeverageMixin]
* △ △
* │ │
* └───┬───┘
* │
* [Curry]
*/


public interface EatingMixin
{
string Name { get; }
}
public static class EatingExtensions
{
public static string Eat(this EatingMixin self)
{
return self.Name + "(゚Д゚)ウマー";
}
}

public interface FoodMixin : EatingMixin
{
}
public static class FoodExtensions
{
public static string GetFoodInfo(this FoodMixin self)
{
return self.Name + "は食べ物!";
}
}

public interface BeverageMixin : EatingMixin
{
}
public static class BeverageExtensions
{
public static string GetBeverageInfo(this BeverageMixin self)
{
return self.Name + "は飲み物!";
}
}

public class Curry : FoodMixin, BeverageMixin
{
public string Name
{
get { return "カレー"; }
}
}

class Program
{
static void Main(string[] args)
{
var curry = new Curry();
Console.WriteLine(curry.Eat()); //カレー(゚Д゚)ウマー
Console.WriteLine(curry.GetFoodInfo()); //カレーは食べ物!
Console.WriteLine(curry.GetBeverageInfo()); //カレーは飲み物!
}
}

はい。ためになったね~。これ。ためになったよ~。というわけで、みなさま賛否両論あったと思いますが、ここまででネタ終わりでございます。(©もう中学生)

関連記事

[C#] Func で BeginInvoke

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

BackgroundWorker を使うのはなんか違うなと思って。いや、違うっていうか、僕の好みじゃないというデタラメな理由だけど。んー、めんどくさいけどデリゲートでマルチスレッド実装してみるかな、とやってみたら簡単だったという。すげぇな C# 3.0。

僕はデリゲート型ってやつを定義するのがすごく嫌いで、めんどくさくて。でも、C# 3.0 からは Func デリゲートがあるので、それを定義しなきゃいけないような場面はほとんどなくなってしまったようだ。そしてもちろん、Func だって当然 BeginInboke メソッドを持っている。非同期デリゲートによるマルチスレッドがさらにシンプルに書けるようになったってことに、いまさらながら気がついた。

で、想定するのは Outlook の送受信みたいなの。時間のかかる処理をバックグラウンドで走らせつつ、でもフォームの操作は無効化する、みたいな。結局、同期処理的に走らせはするものの、フォームが応答しないというか再描画されないような状況になるのはヤダっていうアレ。

以下、文字列を与えるとその長さを返すメソッドを別スレッドで走らせるという、ろくでもなく役に立たないコードでそれを示す。

public partial class Form1 : Form
{
private Func<int> asyncFoo;

public Form1()
{
InitializeComponent();
}

private void Foo(string s)
{
this.Enabled = false;

this.asyncFoo = delegate() {
System.Threading.Thread.Sleep(2000);
return s.Length;
};
this.asyncFoo.BeginInvoke(new AsyncCallback(FooCallback), null);
}

private void FooCallback(IAsyncResult ar)
{
var i = this.asyncFoo.EndInvoke(ar);

this.Invoke((MethodInvoker)delegate() {
MessageBox.Show(i.ToString());
this.Enabled = true;
});
}

private void button1_Click(object sender, EventArgs e)
{
this.Foo("hogehoge");
}
}

なんだかすごく簡素に見えたんだ。僕には。

Q

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

お前は賢いかと問われれば、それがわからぬほど愚かではありませんと答えようか。

とりあえず僕の頭が悪いのは間違いがないとして、だとすれば言語実装という僕にできるはずもないそれに携わる人が考えていることなどは僕の考えの及ばぬ領域であって、じゃあなにか新たにその言語に実装されたフィーチャーはきっと僕が思い巡らす余地もなく熟慮され議論され追加されたものであるのは疑う余地のないものであるだろうし、だったらば正式リリース版で追加された新機能を取り上げてはそれは違うやら道を誤ったやら僕が言うのは明らかに分不相応に思え、いや確かにそうパンピーが発言をすることはとがめられることでもなくむしろどんどん意見すべき筋合いのものであるとは思うのだけれど、しかし無頓着にのうのうと無能な頭で考えた幼稚な休むに似たるそれを小出しにしてみたところでタガタメだよって、けれども被害者にも加害者にもならないモノに価値はあるのかいって、ああ話がそれたのでもとい、要は世の中ってのはジレンマやトレードオフの中にあって間違いや矛盾を含みながらもそれをゆっくりとゆっくりと是正し中和していくようなものであるのだろうという感があって、遥か高みを行くあの賢そうな人たちが下した決断、いま施すことのできる最善の策というのがそれだったんならば、それに続く者がそれを無碍に否定するというのは明らかに違う、確かに疑いという名の予防線を張りながらもでもまずはそれを賞賛のもとに受け入れて行くのが次善の策だろって、表と裏、善と悪、その両面のヤバさにリーチさせながらもベースとしては節操もないほどに活用する方向に身を置くのが最先端の風を感じるためのレシピだろって、なんだか結局よくわからないけどもわからないながらもそれがいま僕が考え得る優秀さにリンクするための唯一の術であるように思え。

この文章をクエリしてみたところで得られるものなんてなにもないぜ。

答えはひとつじゃないんだから。銀の弾丸なんてないのだから。だから。

構造体と僕

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

時には昔の話を。

ふつうはポインタの方を難しいと感じるらしい。ポインタ(メモリの番地)なんてもんは基礎だ。五大装置やらなんやらとハードウェアについての知識を、あるいは CASL を、2 種対策と称して真っ先にやらされた身にとっては、それはそれほど難しい話ではなかった。そりゃもちろん、僕は C の専門家ってわけでも、それを使いこなしまくってるってわけでもなかったから、ポインタのポインタとかの問題を出されればそれはそれで脳みそがおっついてかないという感覚はあったけど、それでもやっぱり難しいという感覚をそれに抱くことはなかったのだ。

でも、構造体は難しかった。難しいといっても観念がわからないとかその使い方がわからないとかいう類じゃあない。なんでそんなもんが存在すべきなのかがわからなかったのだ。だってそうでしょ、アイツって、束ねてるだけじゃん。別々に変数を作ればそれでいいことじゃないか。ダブついているようで気に入らなかったんだ。いちいち新たなる型を作ることは単におっくうでしかないように思えた。

僕はいつも合理性について考えていた。オーバーラップするような機能は許容できなかった。結局のところ値なんてもんは、最終的にはプリミティブな組み込み型に帰結するにも関わらず、高々それを束ねるだけのものにいかほどの意味があろうかとも思えた。考えてみれば、プログラミング言語においても利便性のために存在する機能ってのは多くあるのだけれど、当時の僕はそんなこと知る由もなかった。

まさに初めて学んだ高級言語が C 言語だった。まだ、実装すべき機能は関数という最小単元によって作り上げられるといったような、プログラミングのセオリーさえままならない頃だ。僕はまだたいしたサイズのプログラムを書いたことがなかった。ゆえに、複数の値を意味のあるまとまりに束ねることに、どれほどの価値があるか理解できなかったのだ。あるいは、Windows API などにもっと早く触れていれば、考え方もすこしは変わっていたのかもしれないが。

すこし後になって、複数の戻り値を返したい場合には構造体が有効かもしれないと思うようになった。引数と違って、戻り値が複数の値であるためには、それはひとつの型としてまとまっている必要がある。さすがに、複数の戻り値を返すためにポインタ渡しの引数を乱発したり、そのためにグローバル変数を使ったりするのは違うなと思えた。

「構造体」という名前もややこしかった。VB よろしく「ユーザ定義型」と言ったほうが平易に思えた。「C の構造体が、抽象的な複合型を表現するだけでなく、メモリ上のレイアウトの指定にも使われていた」というようなことをなんとなくにも意識するようになるのは、気まぐれに買ったを読んでからのことであり、僕がその名を妥当と評価するのは「C 言語は中級言語である」とする解釈を知るときまで遅延された。

構造体の意義を知る前に、僕はオブジェクト指向について学ぶようになっていた。これからは Java の時代らしいと聞いたんだ。複数の値をひとまとめに束ねることは、もはや当然のこととして受け入れるようになっていった。OOP のコーディングじゃ、クラスを作らなきゃ始まらなかったからだ。このときの僕には OOP イコール Java だった。なにを書くにもクラス、クラス。構造体がわからないと言っていたのも、過去のこととして過ぎ去ろうとしていた。だってそこに構造体と呼ばれるものは何もなかったのだから。

そうこうしているうちに .NET だ。C# だ。C# には構造体がある。クラスは参照型で、構造体は値型で。そして値渡しに参照渡し。真に高級な言語について学ぶことで、スタックとヒープのような低レベル観念の権化とも呼べるものを理解する羽目になるとは思ってもなかった。K&R の呪縛だかノイマン脳の恐怖だかはともかく、どこまでいってもその辺の知識からは逃れられないのだなと感じ、つかマウス イヤーってなんスかねと思ったりもしたのだった。

クラスなんて構造体に関数ポインタを持たせたモジュールで、インスタンス化ってのはそのポインタを得ることでしかないよという糖衣錠を飲み込んで、逆説的に構造体を理解し始めた頃、僕は関数型の潮流を目の当たりにしていた。休むに似たりの考えをどうにかしようと横好きに流れて下手を打ち、古人の手の上で踊る阿呆は踊らにゃ損々とお堂をめぐる。そこで僕が見た景色は、構造体が直積型と呼ばれる世界だった。

ユーザ定義の抽象データ型には、直和型と直積型がある。そこに潜む集合論(あるいは圏論)にアカデミックなフレーバーを感じた。無論、僕にはそれをテイスティングすることはできないし、だからソムリエにはなれない。だいたいビールさえあればニコニコしてるオヤジ趣味だ。けれど、それを飲んで「あ、なんか洒落た味じゃん」ぐらいのことは言えるし、にわか野郎にも程があるといわれようとも、それを毎晩の献立の中に取り入れることができればバリエーションが広がる。たまには違うモノだって飲みたいのだ。

そんなこんなでまあ浅はかなことを今日もまた考えている。それにしてもアイツとの距離が一向に縮まらなく思えるけれど、それはきっと渦を描きながらゆっくりと外堀を埋めるように近づいていってるからだ、と自分に言い聞かせながら。

[C#] 入力データとしての Excel ファイル

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

ヤツをやっつけるにはどうすればいいだろう。それが表形式データとして整ったものであるならば、OLE DB なり ODBC なりのプロバイダ経由で、Microsoft Jet データベース エンジンに読み込み処理を丸投げしてしまえるだろう。しかし、注意書きやら補足やらタイトルやらなんやらかんやらイラナイ データまでくっついてくるのが常なのであり、そしてそれまでもプログラム側でイイ感じに処理してほしいという要求が当然のなりゆきなのであり、つまり Excel は文化。

なんか最後、結論を間違ったような気がするけど気にしない気にも留めない。こうなりゃもう CSV に変換してしまえい。とりあえずテキストで。なにはなくともテキストで。ていうか遅延バインディング書きたかっただけで。そんなノリで。

namespace Sample.LateBindingExtensions
{
using System;
using System.Reflection;

public static class Invoker
{
public static object CreateObject(string progID)
{
return Activator.CreateInstance(Type.GetTypeFromProgID(progID));
}

public static object InvokeMethod(this object self,
string name, params object[] args)
{
return self.GetType().InvokeMember(
name, BindingFlags.InvokeMethod, null, self, args);
}

public static object SetProperty(this object self,
string name, params object[] args)
{
return self.GetType().InvokeMember(
name, BindingFlags.SetProperty, null, self, args);
}

public static object GetProperty(this object self,
string name, params object[] args)
{
return self.GetType().InvokeMember(
name, BindingFlags.GetProperty, null, self, args);
}
}
}

namespace Sample
{
using System;
using System.Collections;
using System.IO;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using Sample.LateBindingExtensions;

internal enum XlFileFormat
{
xlCSV = 6,
xlCSVMac = 22,
xlCSVMSDOS = 24,
xlCSVWindows = 23,

xlTextMac = 19,
xlTextMSDOS = 21,
xlTextPrinter = 36,
xlTextWindows = 20,
xlUnicodeText = 42,
}

public class ExcelApplication
: CriticalFinalizerObject, IDisposable
{
private object xlApp;
private GCHandle excelHnd;
private Stack localScopeRcws;

private bool cellsFormatGeneral;

#region IDisposable メンバ
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
#endregion

protected virtual void Dispose(bool disposing)
{
if (this.excelHnd.IsAllocated)
{
if (this.excelHnd.Target != null)
{
this.xlApp.InvokeMethod("Quit");
Marshal.ReleaseComObject(this.xlApp);
if (disposing)
this.excelHnd.Target = null;
}
this.excelHnd.Free();
}
}

public ExcelApplication()
{
this.xlApp = Invoker.CreateObject("Excel.Application");
this.excelHnd = GCHandle.Alloc(xlApp);
this.xlApp.SetProperty("DisplayAlerts", false);
this.xlApp.SetProperty("ScreenUpdating", false);

this.localScopeRcws = new Stack();
this.cellsFormatGeneral = false;
}

~ExcelApplication()
{
this.Dispose(false);
}

public bool CellsFormatGeneral
{
get { return cellsFormatGeneral; }
set { cellsFormatGeneral = value; }
}

private object NewRcw(object rcw)
{
this.localScopeRcws.Push(rcw);
return rcw;
}

private void ReleaseRcws()
{
foreach (object rcw in this.localScopeRcws)
{
try
{
//if (rcw != null && Marshal.IsComObject(rcw))
// for (var i = 1; i > 0; )
// i = Marshal.ReleaseComObject(rcw);
if (rcw != null && Marshal.IsComObject(rcw))
Marshal.ReleaseComObject(rcw);
}
catch { }
}
this.localScopeRcws = new Stack();
}

private void WorksheetToCsv(
string bookPath, object sheetID, string csvPath)
{
var xlBook = default(object);
try
{
var xlBooks = NewRcw(this.xlApp.GetProperty("WorkBooks"));
xlBook = NewRcw(xlBooks.GetProperty(
"Open", bookPath, Type.Missing, true));
var xlSheets = NewRcw(xlBook.GetProperty("Worksheets"));
var xlSheet = NewRcw(xlSheets.GetProperty("Item", sheetID));
if (this.cellsFormatGeneral)
{
var xlCells = NewRcw(xlSheet.GetProperty("Cells"));
xlCells.SetProperty("NumberFormatLocal", "G/標準");
}
xlSheet.InvokeMethod("SaveAs", csvPath, XlFileFormat.xlCSV);
}
finally
{
if (xlBook != null)
xlBook.InvokeMethod("Close");
this.ReleaseRcws();
}
}

public void WorksheetToCsv(string bookPath)
{
var csvPath = Path.Combine(
Path.GetDirectoryName(bookPath),
Path.GetFileNameWithoutExtension(bookPath) + ".csv");
this.WorksheetToCsv(bookPath, csvPath);
}

public void WorksheetToCsv(string bookPath, string csvPath)
{
this.WorksheetToCsv(bookPath, 1, csvPath);
}

public void WorksheetToCsv(
string bookPath, int sheetIndex, string csvPath)
{
this.WorksheetToCsv(bookPath, (object)sheetIndex, csvPath);
}

public void WorksheetToCsv(
string bookPath, string sheetName, string csvPath)
{
this.WorksheetToCsv(bookPath, (object)sheetName, csvPath);
}
}
}

さあ使おう、いま使おう。

namespace Sample
{
using System;

class Program
{
static void Main(string[] args)
{
Console.Write("Excel file: ");
var book = Console.ReadLine();

using (var xlApp = new ExcelApplication())
{
xlApp.CellsFormatGeneral = true;
xlApp.WorksheetToCsv(book);
}

Console.Write("Press any key to exit.");
Console.ReadKey();
}
}
}

[セルの書式設定] をすべて [標準] にしてしまう、というのが今日のハイライト。

関連記事

[C#] タプルの構造的等値および比較

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

.NET 4 から、BCL にタプルが入りました。

組を構築する(Building Tuple)

結局、タプルは参照型として実装されましたが、それはほとんど値型として振る舞うべきものであるようにも見えます。タプルはただ複数の値をまとめるためにあり、興味があるのはそのまとめられた値の方です。となれば、なにか 2 つのタプルを比較するときには、タプルのインスタンスの参照比較ではなくタプルの中身の値によって比較を行いたい、という場合がほとんどではないでしょうか。

Equals および等値演算子 (==) 実装のガイドライン」によれば、参照型においては、Point、String、BigNumber などの基本型を除き、等値演算子 (==) をオーバーロードしない方針がとられるようです。タプルはどうでしょうか。

var x = Tuple.Create(42, "Foo");
var y = Tuple.Create(42, "Foo");
var z = Tuple.Create(42, 256);

Console.WriteLine(x == y); // False
Console.WriteLine(x == z); // Compile Error

タプルは基本型とはみなさない、ということでしょうか。等値演算子によるタプルの比較は参照比較になります。等値演算子を用いた場合は引数に同じ型の値が要求されますが、それ以外は ReferenceEquals メソッドを使った場合となんら変わりません。

Console.WriteLine(Object.ReferenceEquals(x, y)); // False
Console.WriteLine(Object.ReferenceEquals(x, z)); // False

タプルの内容物がそれぞれ等値であるかどうか、つまり構造的等値を評価したい場合は、前述のガイドラインが示すように Equals メソッドを使います。

Console.WriteLine(x.Equals(y)); // True
Console.WriteLine(x.Equals(z)); // False

ここで考えてみたいのは x.Equals(z) です。これは妥当でしょうか。x は Tuple<Int32, String> で、z は Tuple<Int32, Int32> です。このような処理が必要な場面もあるでしょう。しかし、強い静的型付けによる便益を最大限活用しようとするなら、これはなるべく避けたい事態です。そもそも型が違う値どうしが構造的な等値関係になることはありえませんから、コンパイル エラーで弾いてもらった方が賢明でしょう。

そこで、他の手段を考えます。

Console.WriteLine(EqualityComparer<Tuple<int, string>>.Default.Equals(x, y)); // True
Console.WriteLine(EqualityComparer<Tuple<int, string>>.Default.Equals(x, z)); // Compile Error

EqualityComparer<T>.Default プロパティから等値比較子クラス EqualityComparer<Tuple<Int32, String>> のインスタンスを得て、その Equals メソッドで等値比較を行います。EqualityComparer<Tuple<Int32, String>>.Equals メソッドによって、比較する 2 つの値がともに Tuple<Int32, String> 型であることがコンパイル時に保証され、そして構造的な等値比較が行われます。

なお、EqualityComparer<T> は構造的等値のためだけにあるのではありません。あくまで、型 T によって定義された等値比較を行うためのものです。タプルに定義された等値比較が構造的等値を行うものであるので、このような結果が得られるのです。

さて、等値比較とあわせて大小比較についても見ておきましょう。比較対象が同じ型であることをコンパイル時に保証し、かつ、構造的比較を行うコードです。

var x = Tuple.Create(1, 0);
var y = Tuple.Create(1, 9);
var z = Tuple.Create(2, 0);
var foo = Tuple.Create(1, "Foo");

Console.WriteLine(Comparer<Tuple<int, int>>.Default.Compare(x, y)); // -1
Console.WriteLine(Comparer<Tuple<int, int>>.Default.Compare(x, x)); // 0
Console.WriteLine(Comparer<Tuple<int, int>>.Default.Compare(y, x)); // 1
Console.WriteLine(Comparer<Tuple<int, int>>.Default.Compare(y, z)); // -1
Console.WriteLine(Comparer<Tuple<int, int>>.Default.Compare(x, foo)); // Compile Error

EqualityComparer<T> クラスによる等値比較とほとんど同じです。Comparer<T>.Default プロパティから比較子クラス Comparer<Tuple<Int32, Int32>> のインスタンスを得て、Compare メソッドで比較を行います。

IComparable インターフェイス経由で比較を行うこともできますが、タプルが実装しているのは IComparable<T> でなく IComparable ですので、比較対象の値の型が異なる場合は実行時に例外がスローされます。ほとんどの場合、タプルの比較は Comparer<T> で行うのがよいでしょう。

Console.WriteLine(((IComparable)x).CompareTo(y));     // -1
Console.WriteLine(((IComparable)x).CompareTo(foo)); // ArgumentException
Console.WriteLine(((IComparable)x).CompareTo("foo")); // ArgumentException

このとき、タプルを IComparable にキャストするのを忘れないでください。タプルでは、IComparable.CompareTo メソッドを明示的に実装しているので、キャストしなければ呼び出すことができません。

最後に、この記事のサンプル コードでは考慮していませんが、EqualityComparer<T>.Default および Comparer<T>.Default を大量に呼び出すようなケースでは、それらをいったん変数に代入しておいた方がパフォーマンスがよいかもしれません。

Conclusion

.NET のジェネリックを十分に使いこなしている方々にとっては、なにをいまさらといった話かもしれません。しかしながら、僕が今回書いたようなことにずばり言及している情報は、すこしググっただけでは見つかりませんでした。

知らなければ、ただ回りくどいコードを量産していたかもしれません。

var x = Tuple.Create(42, "Foo");
var y = Tuple.Create(42, "Foo");

Console.WriteLine(x.Item1 == y.Item1 && x.Item2 == y.Item2);

便利なものは便利なままに使いたい。タプルというとても便利なクラスが BCL 入りしたことで、EqualityComparer<T> や Comparer<T> は、これから、よりいっそう使われていくことになるのではないでしょうか。


enum を Dictionary に変換

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

列挙型の列挙子を列挙したいというのは割とよくある話で、列挙子の名前と値を辞書として取り出すメソッド(関数)を作っておくと便利です。

at C#

現行最新版の C# 3.0 で。型パラメータに列挙型(enum)とその基になる型(underlying type)を指定して IDictionary を返す、静的メソッドを定義します。

using System;
using System.Collections.Generic;

static class EnumEx
{
public static IDictionary<string, TValue> EnumToDictionary<TEnum, TValue>()
where TEnum : struct, IComparable, IFormattable, IConvertible
where TValue : struct
{
var etyp = typeof(TEnum);
if (!etyp.IsEnum)
throw new Exception("TEnum is not enum.");
var vtyp = typeof(TValue);
if (!vtyp.Equals(Enum.GetUnderlyingType(etyp)))
throw new Exception("TValue is not underlying type of TEnum.");

var dic = new Dictionary<string, TValue>();
foreach (TValue val in Enum.GetValues(etyp))
dic.Add(Enum.GetName(etyp, val), val);
return dic;
}
}

C# では、型パラメータの制約に enum を指定することも(コンパイラ エラー CS1031: 型が必要です。)、System.Enum を指定することも(コンパイラ エラー CS0702: 制約は特殊クラス 'System.Enum'にはなれません。)できません。よって、TEnum の型が enum であるかどうか、また TValue が TEnum の基になる型であるかどうかを、静的にチェックすることはできません。ここでは、ある程度それっぽい型であることを静的に判断しておいて、最後は if 文と例外にゆだねています。

使い方は以下の通り。

enum CardSuit { Spades, Hearts, Diamonds, Clubs }

class Program
{
static void Main(string[] args)
{
foreach (var pair in EnumEx.EnumToDictionary<CardSuit, int>())
Console.WriteLine("{0}: {1}", pair.Key, pair.Value);
}
}

at F#

Visual Studio 2010 から搭載される F# でも書いてみます。

module Enum =
open System

let enumdict<'T, 'U when 'T : enum<'U>> =
let t = typeof<'T>
dict( Enum.GetValues(t) |> Seq.cast<'U> |> Seq.zip (Enum.GetNames(t)) )

F# では 列挙型制約(Enumeration Type Constraint)が使えます。'T が enum であり、かつ、'U が 'T の基になる型であることは、コンパイル時に保証されます。

使い方は以下の通り。

type cardsuit = Spades = 0 | Hearts = 1 | Diamonds = 2 | Clubs = 3

module Program =
open Enum

do enumdict<cardsuit, _>
|> Seq.iter (fun (KeyValue(k,v)) -> printfn "%s: %d" k v)

enumdict<cardsuit, int> とする必要はありません。コンパイラは制約を忘れたりしません。'T さえわかれば、制約によって 'U は自明です。ワイルドカード(プレースホルダ)を置いておけば、型推論が cardsuit の基になる型 'U は int であると導き出してくれます。

Conclusion

なんで C# には enum 制約ないんだろ。

関連記事

書籍『実践 F# 関数型プログラミング入門』を書きました

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

荒井さんじゃない方の著者のいげ太です。



  • 価格:¥3,360
  • 大型本: 464ページ
  • 発売日: 2011/01/07
  • 著者:荒井省三:いげ太
  • 出版社:技術評論社
  • ISBN-10: 4774145165
  • ISBN-13: 978-4774145167

というわけで。詳しい目次は出版社様のサイトからどうぞ。以下、すこしだけご紹介を。

まず、F# とはどんな言語でしょうか。最初は、まったく OCaml に見えます。すこしして、Python にも似て見えてきます。また学び進めていくと、結局 C# だなと思えてきます。あるいは VB の方が近いようにも思えてきます。そしてそのうちに、Haskell を連想したり、さらには Erlang や Scala に相通ずるものをも感じます。

そして最後に気づくでしょう。これは F# だと。

F# はおもしろい言語です。さまざまな側面を持っています。ベースこそ関数型言語ですが、その実さまざまな言語のいいとこどりをした、先進的な、マルチ パラダイム言語です。あなたがもし好奇心にあふれるプログラマーであるなら、是非一度、触れてみることをお勧めします。コンパイラ言語でありながら対話環境を持つ F# なら、お手軽に始めることができます。

とはいえ、手続き型言語やオブジェクト指向言語といった、メインストリームのプログラミング言語に慣れた方には、F# は、いささか小難しいように感じられるかも知れません。はい、その通りです。そんなことない簡単だ、などと言い張るつもりはありません。

「高ければ高い壁の方が 登った時気持ちいいもんな」と歌ったのはミスチルですが、あなたがプログラマーとして、まだ限界だなんて認めないつもりなら、F# は良き壁となってくれるでしょう。

本書では、F# の言語仕様についてかなり広範に説明しています。しかしまた、初心者の方にとって難しくなりすぎないように、一歩ずつ学び進めることができるように工夫を凝らしました。本書が F# 学習の一助となり、F# の楽しさをすこしでも伝えることができたなら、無上の幸いです。

ところで、執筆にあたっては、たくさんのレビュアーの方々にご協力をいただき、素晴らしいツッコミを多数いただきました。この本がご評価いただけることがあれば、それはレビュアーの方々のご尽力あってこそです。この場を借りてお礼申し上げます。本当にありがとうございます。

一方で、著者の至らなさから、発売後、少なくない数の誤植についてご指摘をいただいております。誠に申し訳ありません。この点につきましては、現在、出版者様のサイトにて公開すべく正誤表を準備しております。公開時期が決まり次第、追ってご連絡いたしますので、もうしばらくお待ちください。

なお、もしなにかツッコミがありましたら、本エントリへのコメントやトラックバック、igeta{dot}net{at}gmail{dot}com 宛のメール、Twitter では専用ハッシュタグ #PracticalFS付き tweet か @igetaへのリプライなどで、お気軽にご連絡ください。

F# 開発チームの面々はいつも、F# の名前の由来を「F is for Fun!」と茶目っ気たっぷりに語ってくれます。F is for Fun! の精神がすこしでも伝わりますように。

Enjoy!

C#er のためのやさしい再帰入門

$
0
0

NOTE: この記事は、当初、ココログの「いげ太のブログ」で公開していたものです。

よく訓練された C# 使いならばご存じの通り、C# に末尾最適化はない。より正確に言い換えるなら、C# 4.0 コンパイラは 'tail.'プリフィックスを付与しない。このことによって、C# プログラミングにおいては、再帰はおよそ避けるべきものとして認識されている。しかし。

しかしポインタと再帰の明らかな重要性以上に重要なのは、これらの学習から得られる精神的な柔軟さと、これらを教えている授業からふるい落とされないために必要な精神的態度が、大きなシステムを構築する上で欠かせないということだ。ポインタと再帰には、ある種の推論力、抽象的思考力、そして何よりも問題を同時に複数の抽象レベルで見るという能力が要求される。そしてポインタと再帰を理解できる能力は、優れたプログラマになるための能力と直接的に相関している。

Javaスクールの危険 - The Joel on Software Translation Project

ならば。もしあなたが再帰の得意でない C#er であるならば。末尾最適化を備えた .NET 言語である F# との対比によって再帰に入門してみるってのはどうか。

はじめに

この記事では、末尾再帰とそうでないふつうの再帰の違いについて述べません。while ループを末尾再帰に書き換えるだけのことを説明します。

while

ありがちな学習用コードを示そう。1 から引数 n までの整数の総和をとる関数 Sum だ。ここでは、ごく簡単な例として示すために for や foreach なんてな高級なループ構文を使わずに、while で書く。

int Sum(int n)
{
var ret = 0;
var i = 1;
while (i <= n) {
ret = ret + i;
i = i + 1;
}
return ret;
}

// Console.WriteLine( Sum(10) ); // 55

対応するように F# コードも示そう。上の C# コードと下の F# コードに、たいした違いがないとわかるだろう。

let sum n =
let mutable ret = 0
let mutable i = 1
while i <= n do
ret <- ret + i
i <- i + 1
ret

// printfn "%d" (sum 10) // 55

変数を減らす工夫

さて上記のつくりでは、1、2、3、4と、数値を1づつ加算してループを行うために、ループ カウンターの i と、どこまで加算するかの終了条件となる n を、それぞれ個別の変数として持っている。

しかしこれは、n、n-1、n-2、n-3と、数値を1づつ減算していって最後は1で終わるようにすれば、引数の n 自体をループ カウンターとして使うことができるので、変数 i をなくしてしまえる。

int Sum(int n)
{
var ret = 0;
while (n > 0) {
ret = ret + n;
n = n - 1;
}
return ret;
}

同じように F# コードも書き直そう。ただしちょっとした問題がある。F# の引数には基本的に再代入ができない。よってここでは、関数内で、引数と同じ名前の再代入可能な変数を定義することでやってみよう。結局、実質の変数は減ってないじゃんって話だけど、気分はいくぶん楽になるだろう。

let sum n =
let mutable n = n
let mutable ret = 0
while n > 0 do
ret <- ret + n
n <- n - 1
ret

変数でなく、引数として

さて、いま引数の n と変数の n は同質化してしまったってわけ。でもどうせなら、こんなまがいもんの同質化じゃなくて、ほんとうに同じものって呼べるようにしてしまいたい。もちろん、F# では引数への再代入ができないから、そう単純にはいかないわけだけど。

// こうしたい 
let sum n =
let mutable ret = 0
while n > 0 do
ret <- ret + n
n <- n - 1
ret

てゆうか、ついでだから ret もなくしてしまおうか。いま関数内の n をなくそうとしているわけだけど、これはつまり、変数の n を引数の n だけでまかなってしまおうってことで、すなわち、関数内にある変数を引数に追い出してしまえば万事オーケーって寸法。なら ret も引数に追い出せばいいんじゃん。

// むしろこうしたい 
let sum ret n =
while n > 0 do
ret <- ret + n
n <- n - 1
ret

// 呼ぶときはこんな感じで
// printfn "%d" (sum 0 10) // 55

じゃあやってみよう。でもどうすれば。再代入をなくせばいい。なぜいま再代入が必要。ループがあるから。ループの代わりを探せばいい。そう、それが、つまり、再帰だ。

繰り返しの条件

でも while は条件判断をもやっている。while ループを使わずに、でも条件判断は行いたい。よろしい、ならば if だ。while の中に隠れている if を引きずり出せ。白日の下に。コードの上に。

let sum ret n =
if n > 0 then
ret <- ret + n
n <- n - 1
ret

としたところで一つ触れておきたいのは、実のところ F# の if は、C# でいうところの条件演算子(3項演算子)の方に近いってこと。cond ? exp1 : exp2 の形で、値を返すアレ。つまり、上記のコードじゃあ exp2 がなくて変だ。ここでは、なにもしないことをあらわす () という値を当て込んで、ひとまずの体面を整えておきたい。

let sum ret n =
if n > 0 then
ret <- ret + n
n <- n - 1
else
()
ret

sumthing else

さて、肝心の再帰呼び出しをまだ書き入れていない。関数の内側からその関数自身を呼ぶんだ、再代入していた ret + n と n - 1 を引数に渡して。

let sum ret n =
if n > 0 then
sum (ret + n) (n - 1)
else
()
ret

しかしこれはおかしなコードだ。なにか変だ。そんな匂いがする。無理やりに突っ込まれた () がアンバランスさを教えている。これは C# で書き直すならこんなコードだ。

int Sum(int ret, int n)
{
(n > 0) ? Sum(ret + n, n - 1) : null;
return ret;
}

() は null ではない。() を null で置き換えるのは無理やりにすぎたのだとしたら、あるいはこんなコードと見立てよう。

int Sum(int ret, int n)
{
if (n > 0)
Sum(ret + n, n - 1);
else
;
return ret;
}

なにか嫌な予感がしてこないか。

再帰

問題の本質はこうだ。while 版の Sum が呼ばれるとき、定義中の return ret が評価されるのは一度っきりだ。しかし再帰版の Sum では、再帰によって Sum が呼ばれる度に return ret が評価される。これはイケナイ。

ということは、return ret が評価されるのを一度っきりにすればいいのであり、つまり、再帰時には return ret を通らないようにする。そんな場所に return ret を置くのだ。そんな場所はどこ。else 節だ。

int Sum(int ret, int n)
{
if (n > 0)
Sum(ret + n, n - 1);
else
return ret;
}

オーケー。まだバランスが悪い。分岐の片側でしか return されていない。再帰呼び出しの Sum の返り値が捨てられている。きっとこうだ。

int Sum(int ret, int n)
{
if (n > 0)
return Sum(ret + n, n - 1);
else
return ret;
}

「きっと」って、アバウトすぎる。なら声に出して読んでみよう。指差し確認、声出し確認。

「n > 0 のときは、ret + n を ret に、n - 1 を n に渡して再度 Sum を呼んでその結果を、そうでないときは ret の値を返す」

ほら、なにも問題ない。

なにも再帰だけ難しく考えすぎる必要はないんだ。ループを使う時に「この条件が成立する間はループの中身を実行して、成立しなくなったらそこから抜けて変数の値を返す」ということだけを意識すればよかったように、再帰だって前述のように読めばよいだけ、「この条件が成立するときは再帰して結果を返す、成立しなかったら引数の値を返す」と認識すればよいだけだ。

重要なのは、どんな条件で再帰して、そして再帰の果てでなにを返すのか。

再帰によって、いずれその果てで得られる最終の結果を返すためには、再帰呼び出し部分にも return 付けておかなければいけないのであり、再帰呼び出し先に結果の算出をお任せしておいてそいつを捨てちゃあ元も子もない。

それ一行で

F# の if は C# の条件演算子により近しいと言った。なら条件演算子を使おう。

int Sum(int ret, int n)
{
return (n > 0) ? Sum(ret + n, n - 1) : ret;
}

そして F# に戻ろう。こうなる。

let sum ret n =
if n > 0 then sum (ret + n) (n - 1) else ret

寄せ

実はまだ上記の F# コードは動かない。再帰関数には rec キーワードを付ける必要がある。

F# では、コードは上から順に定義され、未定義のモノを使用することはできない。また、定義中のモノは未定義状態とみなすのがデフォルトの解釈になるのだけれど、これを定義済とみなすべく rec を付与する。つまり、rec の存在が定義中を未定義とみなすか定義済とみなすか制御可能にするのである。と、込み入った説明をするとそういうことだ。

let rec sum ret n =
if n > 0 then sum (ret + n) (n - 1) else ret

さあ、これで動くコードになった。けどもうちょっとイイ感じに仕上げよう。かっこよく洗練された再帰関数にするのだ。

まずは ret 引数だ。ret じゃよくわからない。この場合は accumulation から acc と名付けるのがよいだろう。そして、このような再帰の間中引き回すためだけの取って付けたような引数は、引数リストの最後に鎮座するのがお定まりだ。

let rec sum n acc =
if n > 0 then sum (n - 1) (acc + n) else acc

次に、再帰呼び出しの位置。別に現状でもなんら問題はないが、せっかく末尾再帰と呼ばれるものを書いたのだから、コード上でも後ろの方にあった方が“この例の場合は”それっぽく見えていいと思える。if の条件をうまくひっくり返して、then 節と else 節を入れ替えよう。

let rec sum n acc =
if n <= 0 then acc else sum (n - 1) (acc + n)

最後に、sum の呼び出し側を考える。いま sum 関数の呼び出しは、たとえば10までの総和であれば、sum 10 0 のように行う必要がある。そのくせ第2引数は常に0でよい。常に0でよいのなら、常に0を与えるような関数でラップして、sum 10 で呼び出せるようにするべきだ。

let sum n =
let rec f n acc = if n<=0 then acc else f (n-1) (acc+n)
f n 0

おつかれさま。これでできあがり!

VBA を一括エクスポート/インポートする Tsukeawsase というツールを作った

$
0
0

VBA のなにがめんどくさいって、コードとリソースが一体となっているところである。Excel ならワークシート、Access ならテーブルとかもろもろのオブジェクトと VBA コードとが、単一のバイナリ ファイルに格納されるところだ。ある意味でそれは簡便さであるのだけど、その代償は大きい。VBE の貧弱さと相まって、プログラマのイライラと毛嫌いを誘発する元ともなっている。

いちおう VBA コードのインポート/エクスポートもできるけど、1モジュール/オブジェクトずつ、ちまちまとしかできない。そんなら一括インポート/エクスポートできればいいんじゃね、というわけでスクリプトを作った。CodePlex にて公開。

http://tsukeawase.codeplex.com/

こういうものを作られている方は他にもいらっしゃるのだけど、僕のほしいものとはちょっと違っていたので作った。僕のほしかったのは、コマンドから実行でき、Excel でも Access でも使える単一のツールだ。

使い方。まずは [Download] を。ダウンロードした zip を展開すると入っている Tsukeawase.wsf と Tsukeawase.partial.js が本体。Tsukeawase.wsf が main 側で、Tsukeawase.partial.js はライブラリ。そして bin フォルダが Excel/Access ファイルの格納場所となっている。

で、bin の中にあらかじめ入っているサンプルの Excel マクロ有効ブック、Book1.xlsm を使って、まずは VBA コードの全エクスポートを試してみよう。

ちなみに、Excel 2010 の場合、[ファイル オプション] - [セキュリティ センター] - [セキュリティ センターの設定] - [マクロの設定] から [開発者向けマクロの設定] で [VBA プロジェクト オブジェクト モデルへのアクセスを信頼する] のチェック ボックスをチェックしておく必要がある。もちろんこれを有効にするとセキュリティが弱まるので、自己責任で。

以下のようにスクリプトを実行すると、bin フォルダ直下にある Excel/Access ファイルに含まれるモジュール/オブジェクトが全部まるっとエクスポートされる。エクスポートされたファイルは、src フォルダ下に Excel/Access ファイルと同名のフォルダが作られ、そこに作成される。\bin\Book1.xlsm なら \src\Book1.xlsm\ フォルダ下にファイルができる。


cscript //nologo //job:push tsukeawase.wsf

反対に、以下のようにスクリプトを実行すれば、全インポートできる。


cscript //nologo //job:pull tsukeawase.wsf

ここで、これらの操作は同期的に行われる。どういうことかというと、たとえばエクスポートの前に、src フォルダ内のファイルがすべて消されるということで、つまり、\bin\Book1.xlsm が含む VBA コードと、\src\Book1.xlsm\ に格納されるファイルとが同期するようになる。逆(インポート)もまたしかり。ただし、xlsm や accdb を単に削除するようなことはなく、それに含まれる VBA コードが削除の対象となる。ワークシートやテーブルが消されることはない。

しかしそれじゃあなんか不安な気もする、ということで、それぞれの操作の前にバックアップ処理を行うようにしている。それぞれの操作における上書きされ側、つまり、エクスポート(push)であれば src の内容が、インポート(pull)であれば bin の内容が、backup フォルダにバックアップされる。スクリプトの実行ごとに backup の内容が増えていくので、いらなくなったら自分で都度削除する。

というわけで、非常に誰得・俺得なツールであり、また現状 Alpha Release ですが、気が向いたらブラッシュアップしていきますたぶんきっと。

書評「C#プログラミング入門」

$
0
0

著者の出井秀行さんから献本いただいた。Web では Gushwell さんとお呼びした方が通りがよいだろう。窓際プログラマーの独り言あらため Gushwell's C# Dev Notes、あるいは C# プログラミングレッスンで著名なあの Gushwell さんの著書である。

C#プログラミング入門
  • 価格:¥2,415
  • 単行本: 319ページ
  • 発売日: 2011/5/12
  • 著者:出井秀行
  • 出版社:工学社
  • ISBN-10: 4777515982
  • ISBN-13: 978-4777515981

目次など記載の出版社様ページはこちら、Gushwell さんご自身によるサポート ページはこちらから。

一言でいえば、新人にはとりあえずこれ渡しとけば OKの一冊である。

C# の学習資料は Web に充実している。DOBON.NETしかり、++C++;// 未確認飛行 Cしかり、そしてもちろん先に上げた Gushwell さんのブログ、メルマガしかりである。ただし一方で、パラパラとめくり、付箋でメモりながら、時と場所を選ばずに読むことができるという優位性が紙の本にはある。あるいは逆に、コピペできない不便さが、サンプル コードの写経への強制力として働くかもしれない。

しかし Gushwell さんの語り口はおもしろい。ともすれば淡々と事務的に書かれているようで、どこかウェットな、人情味のあるやさしさをたたえている。なんとも不思議な文章を書く人である。そしてそういった文章は、実に初心者にフィットする。

C# という言語は進化の著しい言語だ。いまや当初から比べれば仕様もずいぶん大きくなった。それは、300ページそこそこの単行本サイズには土台おさまるものでもない。

ところが Gushwell マジックである。なぜだかよくわからないが、この本にはそれが無理なく収まっている。値型と参照型、ボックス化、ポリモーフィズム(多態性)、そしてラムダ式や LINQ のみならず dynamic まで。初心者が過不足なく学べるよう巧妙に配慮され、あまり深入りせず、けれど躓きやすいポイントにはコラムで先回りした文章たちが、整然と並んでいるのである。

また、本題に入る前に Visual Studio によるデバッグ法に触れられているのは C# ならではといったところ。IDE を使用したブレークポイントの使用、それにステップ イン、ステップ オーバー、ステップ アウトについての解説がまずある。実際のコードの動きを知ることの大切さ、動かしながら学ぶことのたのしさが強調されている。

ただ一点注意しておきたいのは、本書の冒頭「本書を読むにあたって」にも書かれているが、まったくのプログラミング初心者を対象とした本ではないというところである。「『変数』『代入文』『演算子』『条件分岐文』『繰り返し文』について理解していることを前提としています」とあり、個人的にすこし付け加えると、なんとなく使ったことあるだろう程度には配列についての知識を期待しているように思われる。

一見すると対象読者が狭いようにも感じられる、が、実際のところこれって、いわゆる新人さんにはよくありがちなスキル セットではないだろうか。プログラミングについて勉強してきました、でもアプリケーションの作成経験は、えーっと、がんばります、みたいな。

だからこれ、入門書として、もっともとは言わないまで、かなりマスをターゲットにしているんではないかなーと僕には思える。というか、そもそもそこさえわからない場合は、先輩が手取り足取り反応を見ながら教えた方がよいだろう。

というわけで、C# をすでに知っている方には読む必要のない本ではある。けれど、不必要な本ではないかもしれない。あなたの後輩のために、一部署に一冊、購入しておいて損はない。いまから C# を学ぼうという方にとって、本書は明らかにすばらしい選択肢だ。

まずはこの薄い本から。秋の夜長に C# をはじめよう。

「プログラミングの魔導書 Vol.2」が濃ゆい

$
0
0

ウワサの書籍、「プログラミングの魔導書 Vol.2」を発売前に拝見する機会をいただいた。ので、ご紹介など。今回のテーマは「言語の進化」だとか。

  • 価格:書籍版:2,000円/PDF版:1,500円/セット:2,500円
  • ページ数: 204ページ
  • 発売日: 2011/10/05
  • 著者: Dave Abrahams、江添 亮、小泉 将久、Masahiro Nakagawa、k.inaba、水島 宏太、shelarcy、菊池 正史、近藤 貴俊、柏田 知洋
  • 出版社:ロングゲート
  • 書籍版ISBN: 978-4-9905296-2-8
  • PDF版ISBN: 978-4-9905296-3-5

というか、のぶひささん著の記事「風とF#とメビウスの輪」のレビューに参加させていただいたわけで、そちらの内容についてまずは。

相変わらず摩訶不思議アドベンチャーといった文体で小気味よく、手始めに F# の特徴的な機能の解説からかいつまんで、それからライブラリーを使用したサンプル コードがちょくちょく引き合いに出されるのだけれどもこれがゴイスー。NaturalSpecFParsec、それに AcceleratorZ3とか。あっついで~。

あと個人的に「おわりに」に書かれている一節が非常に F#er らしいな、とか思った。

新しいものは良いものだ。現状に満足しない技術者達が今日も未来を作っているからである。だから、ひょっとしたら数年後にはより良い言語が生み出されていて、その時の私はもうF#を使っていないかもしれない。先のことはわからないが、

ネタバレしないようひかえめに、中途半端に切り取って引用してみた。続きはぜひ書籍を購入して確かめていただきたい。

そしてもちろん、ほかの記事もすばらしいものばかり。ていうか濃ゆい。Web を検索・徘徊してもいまひとつ出てこなかった情報がこんなにもてんこ盛り。やばい。胸焼けしそう。

D 言語、Scala、Haskell、Coq ときて依存型(Dependent Type)とかまじやばい。てか「Boostを使い倒してTwitterクライアントを作る」で C++ に入門したい。C++ 使えますとか言ってみたい。ホント、刺激的な一冊(いろんな言語に触ってみたい病を発症・悪化させるおそれのあるキケンな一冊でもある)。

しかし単純に買いでしょうと、思うわけで。

F# コンパイラをビルドする

$
0
0

F# Advent Calendar 2011 25日目の記事です。今日が最終日と思いきや、ご好評につきボーナス ステージ突入! とのことで。 もうちっとだけ続くんじゃ。

さて、F# は実に心憎いプログラミング言語です。

たとえば fsi(F# Interactive)、いわゆる REPL と呼ばれる対話型評価環境の存在です。API の動作をちょっと調べたいとき、コード片の実行結果をサクッと見てみたいとき、fsi は実に重宝します。なにより、fsi console を開いて、黒い画面に向かて白い文字列をつかつかと打ち込む行為そのものが、なにかハッカーっぽくてすごく気分のよいものです。

「捕獲・・・完了・・・!」

言っちゃうか、そんなことも。

もう一つ、F# にはハッカー気分に浸ることのできる特徴があります。それは、コンパイラのソースコードが公開されている、それもオープン ソース ライセンスで、ということです。標準ライブラリの詳しい挙動を確認したいとき、必要であれば、ソースコードという生の情報にまでたどることが可能です。そして、ソースはただ参照できるだけではありません。

「君のプログラミング言語で、コンパイラのビルド、できる?」

余裕ッス! そう、F# ならね。

ではでは。まず、F# コンパイラは F# で書かれており、これをビルドするためには、F# + .NET 4 の環境がセットアップされていることを前提とします。

次に、CodePlex 上で公開されるコンパイラ ソースコードを取得します。下記リンクから Source Code ページに飛んで、Last Version 枠内にある Download リンクをクリックしてダウンロードしましょう。

F# PowerPack, with F# Compiler Source Drops

落ちてきた fsharppowerpack-<ChangeSet>.zip 内の compiler\2.0\Aug2011.1 ディレクトリをどこか適当な作業ディレクトリに解凍します。あわせて、ディレクトリ名が Aug2011.1 ではわかりづらいですから、fsharp とでもリネームしておきましょう。

で、ここから実際にビルドしていくわけですが、必要な情報は fsharp ディレクトリ内に同梱の README.html に全部書いてあったりします。で、それをバッチにまとめたものが以下になります。

@echo setting environment
cd %~dp0
call run40.bat

@echo cleaning
cd src
if exist ..\Proto (rmdir /s /q ..\Proto)
if exist ..\Debug (rmdir /s /q ..\Debug)
if exist ..\Release (rmdir /s /q ..\Release)

@echo building a Proto Compiler
msbuild fsharp-proto-build.proj /p:TargetFramework=cli\4.0

@echo for faster future startup (optional)
@rem ngen install ..\Proto\cli\4.0\bin\fsc.exe

@echo building the F# core library and unittests
msbuild fsharp-library-build.proj /p:TargetFramework=cli\4.0
@echo building a bootstrapped F# Compiler
msbuild fsharp-compiler-build.proj /p:TargetFramework=cli\4.0

@echo unit tests for NUnit
@rem msbuild fsharp-library-unittests-build.proj /p:TargetFramework=cli\4.0
@rem msbuild fsharp-compiler-unittests-build.proj /p:TargetFramework=cli\4.0

build.bat など適当な名前を付けて fsharp ディレクトリにこれを保存、そして実行します。ビルドには5分前後かかるので、カプチーノでも作って待ちましょう。すると、fsharp\Debug\cli\4.0\bin に、fsc や fsi や標準ライブラリ群がどっさとできあがります。

そうして最後に、fsi を立ち上げて 1 + 1;; なり任意のコードを実行して、動作確認しておくとよいでしょう。自分でビルドしたコンパイラで実行されるコードには、いつもとは違う、新鮮な感動があります。

今日から君はコンパイラ ハッカー!

ここでは取ってきたソースコードを単にビルドしただけですが、いまや私たちは、コンパイラを、標準ライブラリを、自在に操るチケットを手に入れたのです。思う存分いじくり回しちゃいましょう。

とはいえ、ふつうの F# プログラマーは Visual Studio 搭載の Microsoft 謹製コンパイラを使う・使いたいことでしょう。コンパイラをいじくり倒して、ここはこう改良できるんじゃないかとよいアイデアが浮かんだら、UserVoice サイトがあります。

Visual Studio: Languages - F# – Customer Feedback for Microsoft

UserVoice サイトでは、Visual Studio 次期バージョンへの提案を広く受け付けています。ぜひ、あなたのアイデアをフィードバックしてください。あなたのアイデアが、次期 F# コンパイラに取り込まれるかもしれませんよ?

Enjoy together!


「実践 F#」の正誤表について

$
0
0

大変お待たせして申し訳ありません。書籍「実践 F#」の正誤表が公開されました。出版社サポート サイトで重要な訂正を、荒井さんのブログでさらなる訂正を公開している形となります。

すべての正誤情報を保存・印刷するには不便な格好での公開となり、重ねてお詫び申し上げます。つきましては、正誤情報をスクレイピングして以下画像のような HTML にまとめて整形する F# スクリプトをご用意いたしました。

F# スクリプトは以下の通りです。使用方法は、スクリプトの先頭のコメントに記載しております。よろしければお使いください。

(**
*                          PracticalFS.Errata.fsx
*                                by @igeta
*
* 書籍「実践 F#」の正誤表を取得する F# スクリプトです(以下、本スクリプト)。
*
* 本スクリプトは、Html Agility Pack ライブラリ(ライセンス:Ms-PL)を使用
* します。Html Agility Pack は、http://htmlagilitypack.codeplex.com/ から
* HtmlAgilityPack.1.4.0.zip をダウンロードして使用します。
*
* 本スクリプトを使用するには、まず適当なフォルダーを作成し、その中に本スク
* リプトと、HtmlAgilityPack.1.4.0.zip を解凍してできる HtmlAgilityPack.dll
* など3つのファイルを格納します。
*
*    HtmlAgilityPack.dll
*    HtmlAgilityPack.pdb
*    HtmlAgilityPack.XML
*    PracticalFS.Errata.fsx
*
* そして、「fsi PracticalFS.Errata.fsx」コマンドを実行して、同フォルダー内に
* Errata.html を生成します。
*)

#r@"HtmlAgilityPack.dll"

module Scraper =
    open System
    open HtmlAgilityPack

    let readUri (addr: Uri) =
        use webc = new System.Net.WebClient()
        let strm = webc.OpenRead(addr)
        new System.IO.StreamReader(strm)
    let loadHtml (addr: Uri) =
        let html   = HtmlDocument()
        use reader = readUri addr
        html.Load reader
        html.DocumentNode

    let selectNode xpath (node: HtmlNode) =
        node.SelectSingleNode(xpath)
    let selectNodes xpath (node: HtmlNode) =
        node.SelectNodes(xpath)

    let getHtml (node: HtmlNode) = node.InnerHtml
    let getText (node: HtmlNode) = HtmlEntity.DeEntitize(node.InnerText)

module Program =
    open System
    open System.IO
    open System.Text.RegularExpressions
    open Scraper

    let split (s:string) =
s.Split([|' '; ' '|], StringSplitOptions.RemoveEmptyEntries)
    let pageNum s =
String.Format("{0:000}", Regex.Replace(s, "[^\d]*(\d+).*", "$1") |> int)
    let correct s =
Regex.Replace(s, @"<strong>", @"<strong style=""color: #c00;"">")
    let ssget ss i =
if i < Array.length ss then ss.[i] else""
    
    let fromGihyo () =
        Uri(@"http://gihyo.jp/book/2011/978-4-7741-4516-7/support")
        |> loadHtml
        |> selectNode @"//div[@class='readingContent01']"
    let fromMsdnB () =
        Uri(@"http://blogs.msdn.com/b/shozoa/archive/2012/02/29/practical-f-errata1.aspx")
        |> loadHtml
        |> selectNode @"//div[@id='ctl00_content_ctl00_content']//div[contains(@class, 'post-content')]"
    let getRecords root =
        Seq.zip (selectNodes @"./h4"          root)
                (selectNodes @"./table/tbody" root)
        |> Seq.map (fun (h4, tb) ->
             let xs = h4 |> getText |> split
             let ys = tb |> selectNodes @"./tr/td" |> Seq.map getHtml |> Seq.toArray
             [| pageNum (ssget xs 0); ssget xs 1; ssget ys 0; correct (ssget ys 1) |])

    let writeTable (writer: StreamWriter) =
        writer.WriteLine(@"<table>")
        writer.WriteLine(@"<tr><th style=""width: 36px;"">Page</th><th style=""width: 98px;"">見出し</th><th>誤</th><th>正</th></tr>")
        let xs, ys = getRecords <| fromGihyo (),
                     getRecords <| fromMsdnB ()
        Seq.append xs ys
        |> Seq.sort
        |> Seq.iter (fun arr ->
             writer.WriteLine("<tr>" + String.concat "" (Array.map (fun s ->"<td>"+s+"</td>") arr) + "</tr>"))
        writer.WriteLine(@"</table>")

    let main () =
        let path = Path.Combine(__SOURCE_DIRECTORY__, "Errata.html")
        use writer = new StreamWriter(path)
        writer.WriteLine(@"<!DOCTYPE html>")
        writer.WriteLine(@"<html>")
        writer.WriteLine(@"<head>")
        writer.WriteLine(@"<title>実践F# 関数型プログラミング入門(正誤表)</title>")
        writer.WriteLine(@"<meta http-equiv=""Content-Type"" content=""text/html; charset=utf-8"">")
        writer.WriteLine(@"<style>")
        writer.WriteLine(@"    table { border-collapse: collapse; empty-cells:show; }")
        writer.WriteLine(@"    th, td { border: solid 2px #ccc; }")
        writer.WriteLine(@"</style>")
        writer.WriteLine(@"</head>")
        writer.WriteLine(@"<body>")
        writeTable writer
        writer.WriteLine(@"</body>")
        writer.WriteLine(@"</html>")

    do main ()

Emacs はじめました on Windows

$
0
0

GNU Emacs 24 向けに一部設定を見直しました。

取り急ぎ、最低限必要なだけ設定ファイル書いてた。まったくよくわからないけど、とりあえず動いているように見える。しかし、この、とりあえず動いているように見えるようにするまでの長いこと。

;; init.el -*- mode: emacs-lisp; coding: utf-8 -*-
;;
;; for GNU Emacs 24.x on Windows (ja-JP)
;;

;; バックアップ ファイルを一箇所にまとめる
(setq backup-directory-alist '(("." . "~/.emacs.d/backup")))
;; 警告音・画面点滅を無効化
(setq visible-bell t)
(setq ring-bell-function 'ignore)
;; メニューバーとツールバーの非表示
(menu-bar-mode 0)
(tool-bar-mode 0)
;; スタートアップの非表示
(setq inhibit-startup-screen t)
(setq initial-scratch-message nil)
;; モードラインに行番号・列番号を表示
(line-number-mode t)
(column-number-mode t)
;; 各行の左側に行番号を表示
(global-linum-mode t)
(setq linum-format "%5d")
;; C-h でバックスペース
(keyboard-translate ?\C-h ?\C-?)
;(global-set-key nil)
;; 行頭でのキルは改行を含める
(setq kill-whole-line t)
;; 暫定マーク モードをオフ
(transient-mark-mode 0)
;; タブ文字:幅4、インデントはスペースで
(custom-set-variables '(tab-width 4))
(setq-default indent-tabs-mode nil)
;; 行間
;(setq-default line-spacing 0)
;; スクロール:1行ごと、余白なし、画面スクロール時1行残し
(setq scroll-conservatively 1)
(setq scroll-margin 0)
(setq next-screen-context-lines 1)
;; 対応する括弧の強調表示
(show-paren-mode t)
(setq show-paren-delay 0)
;; バッファ 切り換え時の補完
(iswitchb-mode t)
;; 多国語
(if (featurep 'mule)
(progn ;; 言語
(set-language-environment 'Japanese)
;; 文字コード
(set-default-coding-systems 'sjis-dos)
(setq file-name-coding-system 'sjis-dos)
(prefer-coding-system 'utf-8-dos)
(set-buffer-file-coding-system 'utf-8-dos)
(if (not window-system) (set-terminal-coding-system 'sjis-dos))
(setq process-coding-system-alist
(cons '("cmdproxy.exe$" sjis-dos . sjis-dos)
process-coding-system-alist))
))
;; 行末の空白を強調表示
(setq-default show-trailing-whitespace t)
(set-face-background 'trailing-whitespace nil)
(set-face-underline 'trailing-whitespace "SteelBlue")
;; 全角空白・タブ文字の強調表示
(defface face-zenspc '((t (:background "LightGray"))) nil)
(defface face-tab '((t (:background "gray32"))) nil)
(defvar face-zenspc 'face-zenspc)
(defvar face-tab 'face-tab)
(defadvice font-lock-mode
(before fine-font-lock-mode ())
(font-lock-add-keywords major-mode
'((" " 0 face-zenspc append)
("\t" 0 face-tab append)
)))
(ad-enable-advice 'font-lock-mode 'before 'fine-font-lock-mode)
(ad-activate 'font-lock-mode)
(add-hook 'find-file-hooks
'(lambda () (if font-lock-mode nil (font-lock-mode t))) t)
;; GUI
(if window-system
(progn ;; フォント
(set-face-attribute 'default nil
:family "Consolas"
:height 110)
(set-fontset-font "fontset-default"
'japanese-jisx0208
(font-spec :family "MeiryoKe_Console"))
;; 基本カラー
(set-face-attribute 'default nil
:foreground "LightGray"
:background "Black")
(set-face-attribute 'fringe nil
:background "Gray32")
(set-face-attribute 'linum nil
:foreground "Gray64")
(set-frame-parameter nil 'alpha '(90 nil))
;; フレーム位置
(setq initial-frame-alist
(append (list '(top . 46)
'(left . 96)
'(width . 120)
'(height . 38))
initial-frame-alist))
(setq default-frame-alist initial-frame-alist)
))

日本語版 Windows で GNU Emacs はじめるならまずは黙ってこの init.el 入れとけ! 的なのがほしい。雛形、ほしい。初心者、つらい。

VBA にも C# のような書式指定文字列がほしい

$
0
0

ここで実装する Formats 関数は、かなり手抜きの実装のため、残念なバグがあります。ご注意ください。

VBA は文字列処理が弱いとか。まあ気持ちはわかる。しかし、ねぇ、必要最小限の機能は用意されている。さらに必要なものがあるのなら作りゃいいさ。お手本には事欠かないだろう。あの言語からこの言語へ。車輪なら輸入すればよい。

さて VBA である。Windows である。真似るなら C# あたりが妥当か。僕はこういうことがしたい。

[Immediate]
?Formats("{0:000} {{{1:yyyy/mm/dd}}} {2}", 1, Now, "Simple is best.")
001 {2012/04/10} Simple is best.

C# で言うところの String.Fromat だ。VBA にも Format があるが、こちらはあくまで単一の値の書式指定であり、複数の値を相手にすることはできない。この不便を解消したいのである。

おぜんだて

でどうするか。とりあえず正規表現で何とかなりそうな気もする。とりあえず正規表現。正規表現ならきっと何とかしてくれる。というわけで、VBScript.RegExp なわけだけども、これ、生で使うの、すげーだるい。VBA のオブジェクト全般に言えることだが、コンストラクターが引数を持たないために、いったん New してからいちいちプロパティにチマチマ設定していかなければならず、かったるい、かっこわるい。じゃあどうするかというと、RegExp オブジェクトをさくっと生成するための CreateRegExp 関数をまず書く。

PrivateFunction WithIncrIf( _
    ByVal expr AsVariant, ByVal incif AsVariant, ByRef cntr AsLong _
    ) AsVariant

    If expr = incif Then cntr = cntr + 1
    WithIncrIf = expr
EndFunction

PublicFunction CreateRegExp( _
    ByVal ptrnFind AsString, OptionalByVal regexpOption AsString = "" _
    ) AsObject

    Dim cnt AsLong: cnt = 0
    Set CreateRegExp = CreateObject("VBScript.RegExp")
    CreateRegExp.Pattern = ptrnFind
    CreateRegExp.Global = WithIncrIf(InStr(regexpOption, "g") > 0, True, cnt)
    CreateRegExp.IgnoreCase = WithIncrIf(InStr(regexpOption, "i") > 0, True, cnt)
    If cnt <> Len(regexpOption) Then Err.Raise 5
EndFunction

これでひとまず CreateRegExp("{\d+}", "g") とか書けるようにはなった。だけどまだだるい。まだるっこしい。RegExp オブジェクトの生成はシンプルになったが、こいつにマッチさせて返ってくるのが「Match オブジェクトを含む Matches コレクション」なのだ、煩わしいことに。素直に配列でくれ、文字列の配列でくれ。別にいらんし、FirstIndex とか、Length とか、ほとんどの場合は。なわけで、さくっとマッチするための RegExpGMatchs 関数を書く。

PublicFunction RegExpGMatchs( _
    ByVal expr AsString, ByVal ptrnFind AsString, _
    OptionalByVal iCase AsBoolean = False _
    ) AsVariant

    Dim ret AsVariant: ret = Array()

    Dim regex AsObject: Set regex = CreateRegExp(ptrnFind, IIf(iCase, "i", "") & "g")
    Dim ms AsObject: Set ms = regex.Execute(expr)
    If ms.Count < 1 ThenGoTo Ending

    ReDim ret(ms.Count - 1)

    Dim arr AsVariant: ReDim arr(ms(0).SubMatches.Count)

    Dim i AsInteger, j AsInteger
    For i = 0 To UBound(ret)
        ret(i) = arr

        ret(i)(0) = ms.Item(i).Value
        For j = 1 To UBound(arr): ret(i)(j) = ms(i).SubMatches.Item(j - 1): Next
    Next

Ending:
    RegExpGMatchs = ret
EndFunction

これで RegExpGMatchs("{0} {1} {2}", "{\d+}") とかして (("{0}"), ("{1}"), ("{2}")) てな配列を得られるようになった。RegExpGMatchs("{0} {1} {2}", "{(\d+)}") なら (("{0}", "0"), ("{1}", "1"), ("{2}", "2")) だ。

なんだっけ?

なんのためにこんな、RegExp のラッパーのような、ユーティリティ関数書いてんだっけ? ああ、そう、書式指定文字列。{インデックス番号:書式指定子} の形式で書式文字列を書きたいんだった。そんでそう、正規表現で {インデックス番号:書式指定子} な箇所見つけて Replace かければいいんじゃん、てな目論見なんだった。そうそう。それ。

じわじわじっそう

とはいえ、はじめっから {インデックス番号:書式指定子} のような文字列にマッチする正規表現を考えるのは難しい。だけならまだしも、プレースホルダーをあらわす { } のエスケープ問題について最初から考慮するのは骨が折れる。そんなの後でいい。まずは簡単に。まずは動くものを。なにげにすでに文中で例示しているが、エスケープ非対応な {インデックス番号} 形式の書式指定文字列をサポートするような Formats 関数を書いてみよう。

PublicFunction Formats(ByVal strTemplate AsString, ParamArray vals() AsVariant) AsString
    Dim ms AsVariant: ms = RegExpGMatchs(strTemplate, "{(\d+)}")
    Formats = strTemplate

    Dim m AsVariant
    ForEach m In ms
        Formats = Replace(Formats, m(0), vals(m(1)))
    Next
EndFunction

' [Immediate]
'   ?Formats("{0} {1} {2}", "Foo", "Bar", "Baz")
'   Foo Bar Baz

いけそうな気がする。てか、イケるでしょ、これ。うん、ここから育てる。エスケープ対応はまだ後回しにして、次は {インデックス番号:書式指定子} 形式の書式指定文字列をサポートしてみよう。

PublicFunction Formats(ByVal strTemplate AsString, ParamArray vals() AsVariant) AsString
    Dim ms AsVariant: ms = RegExpGMatchs(strTemplate, "{(\d+)(:(.*?))?}")
    Formats = strTemplate

    Dim m AsVariant
    ForEach m In ms
        Formats = Replace(Formats, m(0), Format(vals(m(1)), m(3)))
    Next
EndFunction

' [Immediate]
'   ?Formats("{0:yyyy/mm/dd}: {1}", Now, "What's going on?")
'   2012/04/10: What's going on?

やほーい。んじゃあとはエスケープに対応するだけ。それぞれ2つ重ねて、{{ とすれば { に、}} とすれば } になるようする。Perl のような先読みができないからって泣かない。あきらめないぜ、ここまできたら。

PublicFunction Formats(ByVal strTemplate AsString, ParamArray vals() AsVariant) AsString
    Dim ms AsVariant: ms = RegExpGMatchs(strTemplate, "[^{]?({(\d)(:(.*?[^}]?))?})")
    Formats = Replace(Replace(strTemplate, "{{", "{"), "}}", "}")

    Dim m AsVariant
    ForEach m In ms
        Formats = Replace(Formats, m(1), Format(vals(m(2)), m(4)))
    Next
EndFunction

' [Immediate]
'   ?Formats("{0:000} {{{1:yyyy/mm/dd}}} {2}", 1, Now, "Simple is best.")
'   001 {2012/04/10} Simple is best.

できたっぽい。

おわりに

VBA が貧弱なことに変わりはありませんが、まあマクロ言語ですし、それにしては割に融通が利く方だと思います。良きにつけ悪しきにつけ(白目

いつ FSharpFunc.Adapt を使うべきか?

$
0
0

この記事は、Stack Overflow のすばらしいスレッド、When should we use FSharpFunc.Adapt?を和訳したものです。和訳文章のライセンスは、Stack Overflow のそれと同じく Creative Commonsに従います。

Question

Looking at the source in FSharp.Core and PowerPack, I see that a lot of higher-order functions that accept a function with two or more parameters use FSharpFunc.Adapt. For example:

FSharp.Core や PowerPack のソースを見ると、“2つ以上の引数を持つ関数”を引数に取るような高階関数の多くで、FSharpFunc.Adaptが使われています。以下に一例を挙げます。

let mapi f (arr: ResizeArray<_>) =
let f = FSharpFunc<_,_,_>.Adapt(f)
let len = length arr
let res = new ResizeArray<_>(len)
for i = 0 to len - 1 do
res.Add(f.Invoke(i, arr.[i]))
res

The documentation on FSharpFunc.Adapt is fairly thin. Is this a general best practice that we should be using any time we have a higher-order function with a similar signature? Only if the passed-in function is called multiple times? How much of an optimization is it? Should we be using Adapt everywhere we can, or only rarely?

FSharpFunc.Adapt のドキュメントは薄っぺらすぎます。これって、同じようなシグネチャの高階関数を持つときはいつでも使うべき、一般的なベスト プラクティスなんでしょうか? それか、渡された関数が複数回呼び出される場合だけ? どのくらいの最適化なんだろう? 私たちが Adapt を使用するべきときとは、できるならどこでもなのか、あるいはごくまれになのか?

Thanks for your time.

お時間を取っていただいてありがとう。

asked Mar 17 '11 at 6:58
Joel Mueller

Answers

That's quite interesting! I don't have any official information (and I didn't see this documented anywhere), but here are some thoughts on how the Adapt function might work.

実に面白い! 私はまったく公式情報を持っていませんが(というか、どこにも文書化されてるのを見たことありませんが)、Adapt 関数がどのように働くのかちょっと考察してみます。

Functions like mapi take curried form of a function, which means that the type of the argument is compiled to something like FSharpFunc<int, FSharpFunc<T, R>>. However, many functions are actually compiled directly as functions of two arguments, so the actual value would typically be FSharpFunc<int, T, R> which inherits from FSharpFunc<int, FSharpFunc<T, R>>.

mapi みたいな関数は、カリー化形式の関数を受け取りますが、これは引数の型が FSharpFunc<int, FSharpFunc<T, R>> のようにコンパイルされる、ということを意味します。しかしながら、多くの関数が実際には2引数の関数に直接コンパイルされる、つまり実際の値は FSharpFunc<int, FSharpFunc<T, R>> を継承した FSharpFunc<int, T, R> になる、というのが普通です。

If you call this function (e.g. f 1 "a") the F# compiler generates something like this:

この関数を呼び出すならば(例:f 1 "a")、F# コンパイラはこんなものを生成します。

FSharpFunc<int, string>.InvokeFast<a>(f, 1, "a");

If you look at the InvokeFast function using Reflector, you'll see that it tests if the function is compiled as the optimized version (f :? FSharpFunc<int, T, R>). If yes, then it directly calls Invoke(1, "a") and if not then it needs to make two calls Invoke(1).Invoke("a").

Reflector を使って InvokeFast 関数を見てみると、(f :? FSharpFunc<int, T, R>) という風に、渡された関数が最適化されたバージョンとしてコンパイルされているかどうかテストしていることがわかります。yes の場合は Invoke(1, "a") を直接呼び出して、そうでない場合は Invoke(1).Invoke("a") という2つの呼び出しを行う必要があります。

This check is done each time you call a function passed as an argument (it is probably faster to do the check and then use the optimized call, because that's more common).

このチェックは、関数に実引数を1つ渡して呼び出すたびに行われます(チェックを行ってから最適化された呼び出しを使用する方がたぶん速いです、その方がより一般的ですから)。

What the Adapt function does is that it converts any function to FSharpFunc<T1, T2, R> (if the function is not optimized, it creates a wrapper for it, but that's not the case most of the time). The calls to the adapted function will be faster, because they don't need to do the dynamic check every time (the check is done only once inside Adapt).

Adapt 関数が行うこととは、任意の関数を FSharpFunc<T1, T2, R> に変換することです(関数が最適化されていない場合、そのためのラッパーを作成しますが、そんなケースはほとんどありません)。adapted な関数は、動的チェックを毎回行う必要がないために、呼び出しがより速くなります(チェックは Adapt 内部で一度だけ行われます)。

So, the summary is that Adapt could improve the performance if you're calling a function passed as an argument that takes more than 1 argument a large number of times. As with any optimizaions, I wouldn't use this blindly, but it is an interesting thing to be aware of when tuning the performance!

で、要するに Adapt は、2つ以上の引数を取る関数に対して引数を渡して呼び出してを何度も繰り返し行う場合に、パフォーマンスを向上させることができます。 任意の最適化と同様に、盲目的に使うようなものではないだろうけど、パフォーマンス チューニングするときには注意が必要な興味深い点ですね!

(BTW: Thanks for a very interesting question, I didn't know the compiler does this :-))

(追伸:とっても興味深い質問ありがとう、コンパイラがこんなことしてるなんて知らなかったよ :-))

answered Mar 17 '11 at 12:33
Tomas Petricek

F# Interactive を独自ビルドしてプロンプトにカレント ディレクトリを表示する

$
0
0

F# Advent Calendar 2012 19日目の記事です。前日のご担当は nomurabbit さんです。

昨年の F# Advent Calendar で「F# コンパイラをビルドする」という記事を書いたものの、一向にコンパイラを弄ろうという人が増えなくて残念な気持ちのいげ太です。確かに、ただビルドするだけでは、あまりおもしろくはないかもしれませんですが。はい。

というわけで、今年も懲りずに独自ビルドなネタで行こうと思います。今年は、去年よりもみなさんの興味を引ける記事になればと思っております。よろしくお願いします。

ちなみに、本記事の更新が遅れましたのは、やんごとなき理由でも年末進行で残業をしていたわけでもなく、Wii U を購入してはじめての本体更新を行っていたためであり、GamePad に向かって「がんばれ」「ありがとう」「ドラミちゃーん」などと話しかけてみたものの更新は遅々として進まず、このような時間のブログ更新となりました次第です。

石を投げられても文句は言えませんが、VBA の仕事は投げて来ないでください。

で、今回行うのは、source drops に少しばかり手心を加えて独自ビルドしてみようというもので、表題の通り、F# Interactive Console のプロンプト文字列にカレント ディレクトリを表示するべく改造を施します。なんとなくシェルっぽくなって、より実用的な fsi.exe になること請け合いです。本当です。いろいろ捗るのでぜひ。

F# コンパイラのビルド方法については、前述した昨年の記事内容と変わりありません。ただし、現在の最新版ソースは compiler\3.0\Sep2012 になっていますので、その点だけは読み替えてください。

コンパイラ、というか対話型シェルを改造するというと大げさに聞こえるかもしれませんが、「プロンプト文字列を変える」ぐらいのお題であればかんたんで、compiler\3.0\Sep2012\src\fsharp\fsi\fsi.fs の700行目あたりにある、FsiConsolePrompt なるこじんまりとした型をほんのすこし変更するだけで済みます。まずは対象のソースを引用します。

//----------------------------------------------------------------------------
// Prompt printing
//----------------------------------------------------------------------------

type FsiConsolePrompt(fsiOptions: FsiCommandLineOptions, fsiConsoleOutput: FsiConsoleOutput) =

// A prompt gets "printed ahead" at start up. Tells users to start type while initialisation completes.
// A prompt can be skipped by "silent directives", e.g. ones sent to FSI by VS.
let mutable dropPrompt = 0
// NOTE: SERVER-PROMPT is not user displayed, rather it's a prefix that code elsewhere
// uses to identify the prompt, see vs\FsPkgs\FSharp.VS.FSI\fsiSessionToolWindow.fs
let prompt = if fsiOptions.IsInteractiveServer then "SERVER-PROMPT>\n" else "> "

member __.Print() = if dropPrompt = 0 then fsiConsoleOutput.uprintf "%s" prompt else dropPrompt <- dropPrompt - 1
member __.PrintAhead() = dropPrompt <- dropPrompt + 1; fsiConsoleOutput.uprintf "%s" prompt
member __.SkipNext() = dropPrompt <- dropPrompt + 1
member __.FsiOptions = fsiOptions

prompt 変数に ">"なるプロンプト文字列が設定されていることがすぐにわかるでしょう。まずはこれを "$ "あたりに書き変えましょう。たった1文字、これを変えてリビルドするだけでも、オレオレ fsi が手に入って割とテンション上がります。みなぎってきた。はいそこ、プロンプト文字列くらい実行時に変更可能なよう作られているべきとか言わない。

カレント ディレクトリを表示させるには、prompt を実際に print する箇所、すなわち Print および PrintAhead メソッドにそれぞれ1行追加します。Directory.GetCurrentDirectory() を print すればよいだけですね。

そこで、fsiConsoleOutput.uprintf "%s" prompt を習って、fsiConsoleOutput.uprintf "%s" (Directory.GetCurrentDirectory()) のような1行をその手前に書き足せばよい、のですが、文字色を変えた方が断然見やすいですので、ここは fsiConsoleOutput.uprintf の代わりに fsiConsoleOutput.ucprintf を使っておくのが粋ってもんでしょう。

とすると、まとめて、FsiConsolePrompt はだいたい以下のように書き換わります。

//----------------------------------------------------------------------------
// Prompt printing
//----------------------------------------------------------------------------

type FsiConsolePrompt(fsiOptions: FsiCommandLineOptions, fsiConsoleOutput: FsiConsoleOutput) =

// A prompt gets "printed ahead" at start up. Tells users to start type while initialisation completes.
// A prompt can be skipped by "silent directives", e.g. ones sent to FSI by VS.
let mutable dropPrompt = 0
// NOTE: SERVER-PROMPT is not user displayed, rather it's a prefix that code elsewhere
// uses to identify the prompt, see vs\FsPkgs\FSharp.VS.FSI\fsiSessionToolWindow.fs
let prompt = if fsiOptions.IsInteractiveServer then "SERVER-PROMPT>\n" else "$ "
let cd () = Directory.GetCurrentDirectory()

member __.Print() =
if dropPrompt = 0 then
fsiConsoleOutput.ucprintf ConsoleColor.Green "%s" (cd ())
fsiConsoleOutput.uprintf "\n%s" prompt
else
dropPrompt <- dropPrompt - 1
member __.PrintAhead() =
dropPrompt <- dropPrompt + 1
fsiConsoleOutput.ucprintf ConsoleColor.Green "%s" (cd ())
fsiConsoleOutput.uprintf "\n%s" prompt
member __.SkipNext() =
dropPrompt <- dropPrompt + 1
member __.FsiOptions = fsiOptions

めんどくさければこれをコピペしましょう。そしてビルドです。むずかしいところは何もありませんよね。

あ、ついでですが、compiler\3.0\Sep2012\src\fsharp\fsi\FSIstrings.txt という文字列リソースがあるのですが、この中の最終行、fsiProductName に起動時に表示される製品名・バージョン情報が設定されていまして、これにちょこっと追記して、自分の名前なんかいれちゃったりしても、オレオレ感が増してよいかと思われます。

てな感じで、コンパイラを弄るとか言ってもパーサーに手を入れるんでもなければ、ぜんぜんこわくないので、ゆるふわな気持ちでどんどん独自ビルドにチャレンジしちゃいましょう。すごい人になれた気分を味わえますおすすめ。

F# Advent Calendar 2012、続いてのご担当は Arigata さんです。F# と農業のコラボを見逃すな!

Viewing all 26 articles
Browse latest View live