Archive - F#

実践 F# 関数型プログラミング入門

共著者の一人であるいげ太さんから献本のお誘いを受け、実践F#を献本頂きました。発売前に頂いたのですが、もう発売日をとっくに過ぎている事実!ど、同時期に書評が並ぶよりもずらしたほうがいいから、分散したんだよ(違います単純に遅れただけです、げふんげふん)

NuGetの辺りでも出しましたが、F#スクリプトは活用し始めています。いいですね、F#。普通に実用に投下できてしまいます、今すぐで、C#とかち合わない領域で。勿論、それだけで留めておくのはモッタイナイところですが、とりあえずの一歩として。実用で使いながら徐々に適用領域を増やせるという、なだらかな学習曲線を描けるって最高ぢゃないですか。

F#を学ぶ動機

最近流行りだから教養的に覚えておきたいとか、イベントがファーストクラスとか非同期ワークフローが良さそうなので知っておきたいとか、私はそんな動機ドリブンのつもりでしたが、そういう動機だと実に弱いんですね!そんな動機から発したもので完走出来たものは今まで一つもありません(おっと、積み本の山が……)。もっと具体的に甘受できる現金なメリットがないと駄目なんだ。そんな情けない人間は私だけではない、はず、はず。

というわけで、実際、動機付けが一番難しいのではないかと思います、「実践」するには。F#の場合「それC#で」という誘惑から逃れるのは難しく、正面から向かわないとでしょう。この図式と対比させられるJava-Scala間では、「それJavaで」とは口が裂けても言えなくて(Java……ダメな子)、学ぶことがそのままJVMの資産を活かしてアプリケーションを書けるというモチベーションに繋がりますが、C#は割とよく出来る子だから。そんなわけかないかですが、本書では、冒頭1章でF#手厚く説明されています。言語の歴史を振り返って、パラダイムを見て、F#とはどういう流れから生まれてきた言語なのか。丁寧だとは思います。

並列計算。マルチパラダイム。うーん、それだけだと請求力に欠けるよね、何故ならF#が関数型ベースのマルチパラダイム言語であるように、C#はオブジェクト指向型ベースのマルチパラダイム言語だから。

などとやらしくgdgdとしててもまあ何も始まらない。いいからコード書こうぜ!といった流れで2章で環境導入の解説(この解説は非常に役立ちでした、F# Interactiveのディレクティブ一覧や、SOURCE_DIRECTORYでパスが取れるとか、F#のソースコードの所在とか)で、あとは書く!と。なんとも雑念に満ちたまま読み始めたわけですが、読み始めるとグイグイ引きこまれました。なんというか、学ぶのに楽しい言語なんですよね、F#。

それと、Visual Studioに統合されたF# Interactiveがとんでもなく便利で。こいつは凄い。私、今までREPLって別にどうでもいいものと思っていたのですよ。コマンドプロンプトみたいな画面で一行一行打っていくの。REPLは動作が確認しやすくてイイとかいう話を耳にしては、なにそれ、そもそもメチャクチャ打ちづらいぢゃん、イラネーヨって。でもVSに統合されたF# Interactiveは、IDEのエディタで書くこと(シンタックスハイライト, 補完, リアルタイムエラー通知)とREPLの軽快さが合体していて、最強すぎる。しかもその軽快さで書いたコードはスクリプトとして単独ファイルで実行可能、だと……!F#スクリプト(fsx)素晴らしい。C#で心の底から欲しいと思っていたものが、ここにあったんだ……!

と、読み始めてたら、普通に楽しい言語だし、並列や言語解析といった大変なところに入らなくても実用的だしで、かなりはまってます。F#いいよF#。始める前に考えてた動機だとかなんて幻でしかなく、始めたら自然に心の奥から沸き上がってくるものこそ継続されるものだと、何だか感じ入ってしまったり。

パイプライン演算子

F#といったらパイプライン演算子。パイプラインは文化。と、いうわけかで、実際、私がよく目にしているF#のコードというのは基本|>で繋ぐ、という形であり、それが実にイイなー。などという憧憬はあるのでF#を書くとなったらとりあえずまずはパイプライン演算子の学習から入ったりなどしたりする。

このパイプライン演算子、書くだけならスッと頭に入るけれど、どうしてそう動くのかが今一つしっくりこなかった、こともありました。関数の定義自体は物凄くシンプルでたった一行で。

// |>は中置換なのとinlineなので正確には一緒ではないですが、その辺は本を参照ください!
let pipe x f = f x

おー、すんごくシンプル。シンプルすぎて逆にさっぱり分からない。型も良くわからない。困ったときはじっくり人間型推論に取り組んでみますと、まず、変数名はpipeであり、二引数を持つから関数。

pipe : x? -> f? -> return?

まだ型は分からないので?としておきます。右辺を見るとf x。つまりfは一引数を持つので関数。

x? -> (f_arg? -> f_ret?) -> return?

fの第一引数はxの型であり、fの戻り値が関数全体の戻り値の型となるので

x? -> (x? -> return?) -> return?

これ以上は型を当てはめることが出来ず、また、特に矛盾なくジェネリクスで構成できそうなので

'a -> ('a -> 'b) -> 'b

となる(’aがC#でいう<T>みたいなものということで)。なるるほど、あまりに短いスパッとした定義なので面食らいますが、分かってしまえばその短さ故に、これしかないかしらん、という当たり前のものとして頭に入る、といいんですがそこまではいきませんが、まあ使うときは感覚的にこう書けるー、程度でいいので大丈夫だ問題ない。

このパイプライン演算子をC#で定義すると

public static TR Pipe<T, TR>(this T obj, Func<T, TR> func)
{
    return func(obj);
}

比較するとちょっと冗長すぎはします。とはいえ、この拡張メソッドは、これはこれでかなり有益で、例えばEncodingのGetBytesなどを流しこんだりがスムーズに出来ます。例えばbyte[]の辺りは変換後に別の関数を実行して更に別の、という形で入れ子になりがちで、かといって変数名を付ける必要性も薄くて今一つ綺麗に書けなくて困るところなのですが、パイプライン演算子(モドき)さえあれば、

// ハッシュ値計算
var md5 = "hogehogehugahuga"
    .Pipe(Encoding.UTF8.GetBytes)
    .Pipe(MD5.Create().ComputeHash)
    .Pipe(BitConverter.ToString);
 
// B6-06-FC-CF-DC-99-6D-55-95-B8-B6-75-DB-EE-C8-AE
Console.WriteLine(md5); // Pipe(Console.WriteLine)でもいいですね

気持ちよく、また入れ子がないため分かりやすく書けます。そのためC#でも最近は結構使ってます。ただ、Tへの拡張メソッドという影響範囲の大きさは、相当な背徳を背負います。というかまあ、共同作業だと、使えないですね、やり過ぎ度が高くなりすぎてしまって。パイプはC#のカルチャーでは、ない。うぐぐ。F#なら

"hogehogehugahuga"
|> Encoding.UTF8.GetBytes
|> MD5.Create().ComputeHash
|> BitConverter.ToString
|> printfn "%s"

このようになりますね。「|>」という記号選びが実に絶妙。ちゃんと視覚的に意味の通じる記号となっているし、縦に並べた際の見た目が美しいのが素敵。美しいは分かりやすいに繋がる。

"hogehogehugahuga"
|> (Encoding.UTF8.GetBytes >> MD5.Create().ComputeHash >> BitConverter.ToString)
|> printfn "%s"

翌々眺めると、関数が並んでるなら合成(>>演算子)も有りですね!びゅーてぃほー。

LINQ

関数型言語といったら高階関数でもりもりコレクション処理であり、そしてそれはLinq to Objectsであり。F#ではSeq関数群をパイプライン演算子を使って組み上げていきます。

[10; 15; 30; 45;]
|> Seq.filter (fun x -> x % 2 = 0)
|> Seq.map (fun x -> x * x)
|> Seq.iter (printfn "%i")

filterはWhere、mapはSelect、iterは(Linq標準演算子にないけど)ForEachといったところでしょうか。

new[] { 10, 15, 30, 45 }
    .Where(x => x % 2 == 0)
    .Select(x => x * x)
    .ForEach(Console.WriteLine); // 自前で拡張メソッド定義して用意する

比べると、|>はドットのような、メソッドチェーンのような位置付けで対比させられようです。違いは、パイプライン演算子のほうが自由。例えば、拡張メソッドとして事前にかっきりと定義しなくてもチェーン出来る。だから、F#にSeq.iterなどがもしなかったとしても、

[10; 15; 30; 45;]
|> (fun xs -> seq {for x in xs do if x % 2 = 0 then yield x * x })
|> (fun xs -> for x in xs do printfn "%i" x)

その場でサクサクッと書いたものを繋げられたり、yieldもその場で書けたり(まあこれは内包表記に近くC#だと別にクエリ式でもいいし、といった感じで意味はあまりないですが)実に素敵。しかし、自由には代償が必要で。何かといえば、補完に若干弱い。シーケンスの連鎖では、基本的には次に繰り出したいメソッドはSeqなわけで、一々Seq.などと打つまでもなくドットだけでIntelliSenseをポップアップさせて繋げていくほうが楽だし、ラムダ式の記法に関してもfunキーワードが不要な分スッキリする。

この辺は良し悪しではなくカルチャーの違いというか立ち位置の差というか。C#のほうがオブジェクト指向ライクなシンタックスになるし、F#のほうが関数型ライクなシンタックスだという。同じ処理を同じようにマルチパラダイムとして消化していても、優劣じゃない差異ってのがある。これは、私は面白いところだなーと思っていて。どちらのやり方も味わい深い。

最後に

C#単独で見た時よりも.NETの世界が更に広く見えるようになったと思います。あ、こんな世界は広かったんだって。今後は、ぽちぽちとF# Scriptを書きつつ、FParsecにも手を出したいなあといったところですね。

大して書けはしませんが、それなりに書き始めて使い出せている、歩き始められているのは間違いなく本書のお陰です。「こう書くと良いんだよ」という、誘導がうまい感じなのでするっと入れました。どうしてもC#風に考えてしまって、それがF#にそぐわなくてうまくいかなくて躓いたりするわけですが、そこで、ここはこうだよ、って教えてくれるので。ちょっとした疑問、何でオーバーロードで作らないの?とかの答えは、載っています。

それと最後にクドいけれど、Visual Studio統合のF# Interactiveは本当に凄い。C# 5.0のCompiler as a ServiceはこれをC#にも持ってきてくれることになる、のかなあ。

NuGetパッケージの作り方、或いはXmlエディタとしてのVisual Studio

linq.js 2.2.0.2をリリースし、今回からNuGetでも配信されるようになりました!linq.js、もしくはlinq.js-jQuery、linq.js-Bindingsで入りますので、是非お試しを。ちなみに更新事項はちょっとBugFixとOrderByの微高速化だけです(本格的な変更は次回リリースで)。

さて、そんなわけでNuGetに対応したので、今回はNuGetのパッケージの作り方、公開のしかたについて解説します。やってみると意外と簡単で、かつNuGetいいよNuGet、と実感出来るので、特に公開するようなライブラリなんてないぜ!という場合でも試してみるのがお薦め(参照先としてローカルフォルダも加えられる)。普通に小さなことでも使いたくなります。そういえばでどうでもいいんですが、私は「ぬげっと」と呼んでます。GeForceを「げふぉーす」と呼ぶようなノリで。ぬげっと!

NuGetを使う

NuGetとは何ぞやか、大体のとこで言うと、オンラインからDLLとかライブラリをサクッと検索出来て、依存関係(これのインストールにはアレとソレが必要、など)を解決してくれた上で参照に加えてくれて、ついでにアップデートまで管理してくれるものです。Visual Studioの拡張として提供されているので、インストールはCodePlexからでもいいですが、VSの拡張機能マネージャからNuGetで検索しても出てきます。

インストールすると参照設定の右クリックに「Add Library Package Reference」というのが追加されていて、これを選択すると、トップの画像のようなNuGetの参照ダイアログが出てきます。最初NuGetが喧伝されていたときはPowerShellでのConsoleでしたが、ご覧のようにGUIダイアログもあるので安心。Consoleのほうが柔軟でパワフルな操作が可能なのですが(PowerShellを活かしたパイプやフィルタで一括ダウンロードとか)、普通に参照してーアップデートしてー、程度ならば別にGUIでも全然構いませんし。

.nupkg

NuGetを通してインストール/参照を行うと、プロジェクトのフォルダにpackages.configが生成されています。しかしこれはどうでもいいのでスルー。.slnのあるディレクトリにpackagesというフォルダも生成されていて、実体はこちらにあります。そこにはパッケージ名のフォルダが並んでいて、中には.nupkgという見慣れないものと、libもしくはContentというフォルダがあるのではないでしょうか……?

nupkgが最終的に作らなければならないもので、実態はただのzip。nupkgと同フォルダにあるlib/Contentはnupkgが展開された結果というだけです。というわけで、適当なパッケージをダウンロードして(linq.jsとかどうでしょう!)zipにリネームして解凍するとそこには……!

_relsとか[Content_Types].xmlとか、わけわからないものが転がってます。これらはノイズです。ようするに、System.IO.ZipPackageを使って圧縮してるというだけの話ですねー、恐らくこれらがある必然性はないです。ただたんに、.NET Framework標準ライブラリだけでZipの圧縮展開をしようとすると、こうしかやりようがなかったという、ただそれだけです。だから早くZipライブラリ入れてください(次辺りに入るらしい)。

大事なのは、.nuspecです。

.nuspec

.nuspec(中身はXml)に、バージョン情報やID、依存関係などが記載されています。といったわけで、自分で作らなければならないのは.nuspecです。これにパッケージしたいファイルや配置場所などの定義を記述し、それをNuGet.exeというものに通すと.nupkgが出来上がる、といった流れになっています。

nuspecの記述には、既存のnuspecを見るのが参考になるかもでしょう。但し、既存のnupkgを落として展開した結果のnuspecはNuGet.exeを通された時点で再加工されているものなので(パッケージ用のファイルの場所などの情報は消滅してる←まあ、絶対パスで記述可能だったりするので消滅してないと逆に困るわけですが)、100%そのまんま使える、というわけではないことには少し注意。

XmlエディタとしてのVisual Studio

では、nuspecを書いていく、つまりXmlを書いていくわけですがエディタ何使います?勿論Visual Studioですよね!Visual Studioは最強のXmlエディタ。異論はない。えー、マジXmlを補完無しで書くなんてシンジラレナーイ!小学生までだよねキャハハ。というわけで、補完全開で書きます。補完さえあればリファレンスなくても書けるし!IntelliSense最強説。

そのためにはスキーマが必要なわけですが、ちゃんと用意されています。NuGet Documentationの下の方のReferenceの.nuspec File Schemaにスキーマがリンクされています。CodePlexのソースリポジトリに直リンクというのが色々潔いですな。

さて、適当に新規項目でXmlを作ったら、メニューのXML->スキーマのダイアログを開き、nuspec.xsdを追加してやりましょう。

そして、とりあえずは<とでも打ってやると補完に!–とか!DOCTYPEなどなどに並んでpackageというものが。これを選択すると、一気にxmlns=”http…”と名前空間まで補完してくれて!更に更に書き進めれば……。入力補完は効くし、必須要素が足りなければ警告出してくれるしでリファレンスとか何も見なくても書ける。

これなら打ち間違えでエラーなども出ないし、完璧。Xmlなんて普通のテキストエディタで気合で書く、とか思っていた時もありました。もうそんなの無理げ。VSバンザイ。なお、FirefoxのアドオンのGUI定義などに使うXULのSchemaなども当然適用出来る - XUL Schema ので、まあ、補完のないテキストエディタなんて使ってたら死んでしまうです。

なお、毎回毎回、スキーマの追加参照するのは面倒くさいという場合は、VSの標準スキーマ参照ディレクトリにxsdを直に突っ込んでおくと、楽になれます。オプション->テキストエディター->XMLでスキーマ、で場所が設定出来ます(デフォルトは %VsInstallDir%\xml\Schemas のようで)

パッケージング

nuspecのリファレンスは.nuspec File Formatに。IDとかVersionとかしか書かないし、項目も少ないしネストもないので書き方というほど書き方はないです。参考までにlinq.js-jqueryのnuspecは

<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
    <metadata>
        <id>linq.js-jQuery</id>
        <version>2.2.0.2</version>
        <title>linq.js - jQuery Plugin Version</title>
        <authors>neuecc</authors>
        <owners>neuecc</owners>
        <requireLicenseAcceptance>false</requireLicenseAcceptance>
        <description>Linq to Objects for JavaScript. This version is plugin integrated with jQuery.</description>
        <language>en-US</language>
        <licenseUrl>http://linqjs.codeplex.com/license</licenseUrl>
        <projectUrl>http://linqjs.codeplex.com/</projectUrl>
        <tags>linq javascript jquery</tags>
        <dependencies>
            <dependency id="jQuery" version="[1.3.1,]"></dependency>
        </dependencies>
    </metadata>
    <files>
        <file src="../../jquery.*" target="Content\Scripts" />
    </files>
</package>

といった感じ。tagsはスペース区切りで入れておくと検索の時にそのワードで引っかかる。dependenciesは依存関係がある場合に記載。対象バージョンの書き方に関してはSpecifying Version Ranges in .nuspec Filesを見て書くべし。

filesは後述するNuGet.exe(コマンドラインツール)でのパッケージング時に参照するファイルを設定。何も記載しない場合はNuGet.exeの実行時引数で解決されるので、どちらでもお好みで、という感じですが、普通はこちらに書いておいたほうが楽な気はします。

ファイル指定のsrcではワイルドカードとして*が使えます。targetのほうは、nupkgにパッケージングされた時の階層の指定。この階層の指定は非常に重要です。「Content」の下に記載すると、プロジェクト直下に対象を配置します。この例では Scripts 下に「jquery.linq.js, jquery.linq.min.js, jquery.linq-vsdoc.js」が展開されることになっています。Scriptsというフォルダ名はjQueryに合わせてあります。勿論、対象は.csでも.txtでも何でも可。

では、普通のC#でのdllのように直下には.dllとか置いて欲しくないし参照設定にも加えて欲しい、という場合はどうするかというとtargetを「lib」にします。すると自動で参照設定に加えてくれます。この「Content」とか「lib」とかってのは名前で決め打ちされてますので、そーいうものだと思うことにしませう。

残るはパッケージ化。まずNuGetのトップからDownloadsタブ(Downloadボタンじゃなく)を選び、NuGet Command Line Toolをダウンロード。このNuGet.exeに対して引数「p ファイル名」でnuspecを指定してやればnupkgが出来上がります。私はnuspecと同じ階層にexeを置いて、ついでにbatファイルに

nuget p linq.js.nuspec
nuget p linq.js-jquery.nuspec
nuget p linq.js-bindings.nuspec

とか書いたのを置いて3個のパッケージを作ってます。この辺は好き好きで。

以上が基本的な感じです。ただたんに参照設定に加える、ファイルを配置する、以上のことをやりたい場合はインストール時にPowerShellスクリプトを実行、なども出来るので色々柔軟に手を加えられそうです。また、.NET Frameworkのバージョンによって参照させるファイルを変える、といったことはフォルダの構成を変えるだけで対応で可能です。例えば.Net4の場合は lib/Net4 に、Silverlightへは lib/SL4 に、といったような感じ。

といったルールなどはNuGet Creating a Packageを見るといいでしょう。また、バージョンのフォルダ分けがワケワカランという場合は既存のnupkgを展開してフォルダ構成を見てしまうのが手っ取り早いかも。Rx-AllやNewtonSoft.Jsonなどなど。

ローカル参照としてのNuGet

nupkgは別にオフィシャルのサーバーだけではなく、個人で立てたサーバーも参照出来ます。また、それだけでなく、ただたんにフォルダを指定するだけでもOKです。

作ったnupkgはこれでテスト可能です。また、頻繁に参照に加えるものはわざわざOnlineに繋げて取ってくるの重い!という場合では一度落としたnupkgをローカルに配置してしまうのも悪くないかもです。テストというだけじゃなく、これは普通に使えますね?今まで参照設定の共通化というとテンプレート作って、程度しかありませんでしたが、これならばいい具合に自由度の効いたものが出来そうです。社内/俺々フレームワーク置き場として活用できそう。

なお、現在は、ローカル参照のパッケージは、GUIのパッケージマネージャだとバージョンが上がってもUpdatesに現れなくてアップデート出来ません。Consoleならば現れるので、ふつーにバグのよう。で、報告されていましたし修正もされていた(今リリースされているのには反映されてないもよう)ので、次のリリースでは直ってるんじゃないかと思われます。

NuGet gallery

せっかく作ったパッケージはOnlineに乗せたいよね!NuGet galleryでパッケージの閲覧・登録・管理が出来ます。よーし、じゃあパパSign Inしちゃうぞー、Registerして、と。やってもいつまでたってもInvalid Passwordと言われてしまいます。あれれ……。

現在は管理者の承認が必要なようで David Ebbo: Introducing the NuGet gallery Registerしたら、Twitterの@davidebbo宛てにapproveして!と言わないとダメぽ。私は「Hi. I registered nuget.org, id is “neuecc” . plaease approve my account.」と、スペルミスしてる適当不躾な@を飛ばしたところ数時間後にSign In出来るようになりました。いつまで認証制なのかは不明ですが、いまんところそんな感じなようです。

まとめ

オンラインで簡単にDLLをインストール出来て便利!というのは勿論ありますが、ローカルで使ってみても存外便利なものです。ぬげっといいよぬげっと。NuPackからNuGetに名前が変わったときは、事情は分かる(NuPackは名前が被ってたらしい)けど、NuGetはないだろ、いくらなんでも。と、思ってたんですが、今は何かもうすっかり馴染んだ気がします。ぬげっと。ぬぱっけーじ。ぬすぺっく。

とりあえず私は今後作るのは勿論、今まで出してきたものも、順次対応させてNuGet galleryに登録していくのでよろしくお願いしま。勿論linq.jsもよろしくお願いしま。今回の2.2.0.1は表には何も更新されてない感じですが、裏側の体制を整えてました。

F#スクリプト(fsx)により、linq.jsからAjaxMinのdllを通し圧縮化と、ついでにjQueryプラグインを生成したり、これまたF#スクリプトでリリース用のZip圧縮をワンクリックで一発で出来るようにしたり。今まで手動でやっていた(そしてミスしまくってた!リリースから10分で撤回して上げなおしとか今まで何度やってきたことか)部分を完全自動化したので、もうミスはありません。そして、自動化されたことによりリリースはミス出すし面倒なので、もう少し色々やってからにするかー、とズルズル後回しにする心理がなくなりました。多分。きっと。NuGet対応したことだしで、当分はアクティブにアップデートしていきます!

そんなこんなでF#スクリプトはぢめました。素晴らしすぎる。あとVS2010とシームレスに完全統合されたF# Interactiveがヤバい。超凄い。こんなイイものがあったなんて……。というわけでF#書きたい欲とF#について色々書きたい欲が、ので次回は実践F#書評です、多分。いや、次々回かも。とりあえず近日中には。とにかくF#は絶対触るべきですね!

おまけ

と、いうわけで、生成を自動化します。F#スクリプトでdllのアセンブリ情報を読み込んでnuspecとnupkgを生成するものを書きました。

#r "System.Xml.Linq"
 
open System
open System.IO
open System.Diagnostics
open System.Reflection
open System.Xml.Linq
 
// 同ディレクトリにNuGet.exeを置いておくこと
// mainにはnuspecへの情報登録に利用するdllを、othersにはその他のものを;区切りで
// パスはこのスクリプトからの相対パス
let main = "bin/Release/ClassLibrary4.dll"
let others = ["bin/Release/System.CoreEx.dll"; "bin/Release/System.Interactive.dll"]
 
let pass p = Path.Combine(__SOURCE_DIRECTORY__, p)
let xn s = XName.Get(s)
 
// Load Assembly
type AssemblyInfo =
    { Id:string; Version:string; Description:string; Company:string }
 
let getAttr<'a> (asm:Assembly) = 
    asm.GetCustomAttributes(typeof<'a>, true) |> Seq.head :?> 'a
 
let info =
    let asm = Assembly.LoadFrom(pass main)
    let name = asm.GetName()
    { Id = name.Name;
      Version = name.Version.ToString();
      Description = (getAttr<AssemblyDescriptionAttribute> asm).Description;
      Company = (getAttr<AssemblyCompanyAttribute> asm).Company }
 
let filename = info.Id + "." + info.Version + ".nuspec"
 
// Build .nuspec
let nuspec =
    let file src = XElement(xn "file", XAttribute(xn "src", src), XAttribute(xn "target", "lib"))
    let delBlank = function "" -> "_" | x -> x
    XElement(xn "package",
        XElement(xn "metadata",
            XElement(xn "id", info.Id),
            XElement(xn "version", info.Version),
            XElement(xn "authors", delBlank info.Company),
            XElement(xn "description", delBlank info.Description)),
        XElement(xn "files",
            file main,
            others |> Seq.map file))
 
nuspec.Save(pass filename)
 
// output .nupkg
new ProcessStartInfo(
    FileName = pass "NuGet.exe",
    Arguments = "p " + filename,
    RedirectStandardOutput = true,
    UseShellExecute = false,
    WorkingDirectory = __SOURCE_DIRECTORY__)
|> Process.Start
|> fun p -> Console.WriteLine(p.StandardOutput.ReadToEnd())

DLLからVersionとかDescriptionとか取れてしまう、.NETのアセンブリがサクッと読み込めるF#いいわー。これだけだと情報は最低限なので、tagとかも入れたければ下の方のXElementを生成している部分に直書きで挟んでやればヨシ。スクリプトの軽快さは良いですね。なので設定というか読み込むファイルも先頭のほうで普通に直書きで指定しちゃっております。

そのまま使ってもいいんですが、ビルド後に実行するコマンドラインに指定してやれば一切の手間暇なく常にフレッシュ。おお、素敵。

F# TutorialをC#と比較しながらでF#を学ぶ

F#はMicrosoft発の関数型言語で、Visual Studio 2010に標準搭載されます。Visual Studio 2010 Beta 2も出たことだし、話題の?新言語を少し勉強してみることにします。F#の新規プロジェクト一覧にTutorialというのが用意されているので、これの中身を、C#と比較しながら見ていきたいと思います。追記:Microsoft Visual Studio 2010 First Look Beta 2日本語版も公開されました。

基本

open System
let int1 = 1
let int2 = int1 + 3
using System;
var int1 = 1;
var int2 = int1 + 3;

名前空間の利用の設定と基本的な変数の代入方法。といったところでしょうか。そのまんまだし、別にC#と違いは特にないっぽい。C#ではvar、F#ではlet。どちらも推論が効くのでほとんど同じ。末尾セミコロンはいらないようです。#lightがどうたらこうたら、というのは略。それともう一つ、F#はこのように定義したint1に再代入は出来ません。int1 = 100とすると、比較になります(==ではなく=が比較)。再代入的なint1 <- 1000はコンパイルが通らない。不変(immutable)なのです。C#だとreadonly、はフィールドにしかつけられないので、同じことを再現するのは無理なよう。

Print

printfn "peekResult = %d" peekResult
printfn "listC = %A" listC

F# TutorialではPrintは最後にあるのですが、あのですね、出来ればprintは冒頭にしていただきたいです。なんというか、私がF#で一番戸惑ったのが、printfn int1ってのが出来ないことなんですね。いやほら、とりあえずlet int1 = 1って書いたじゃないですか、最初に。で、書いたらとりあえず表示して確認したいでしょ?Console.WriteLineにあたるのはprintfnか、って来るわけです。でも、書いても動かないの。で、まあ、つまるところstring.Format的なものであり書式指定が必要、というところまで行くわけですが、そこで書式って何を書けばいいの?ということになるわけです。”%d”とか予告なく言われても分からないし。もうブチ切れですよ。え、Cの書式指定と一緒だって?いやあ、Cの書式指定も全然覚えてられません、あれあんま良くないと思うんですが……(ついでに言えば私はC#の書式指定も全然覚えてない、必要な度にMSDN見に行ってる)。しかもF#のは書式も色々拡張されてない?より一層分からん!int1の出力で挫折する!

ということなので、もっと頭の方にprintfnのきちんとした解説を載せてくれないと辛いです。ただ、どうしてもアレならConsole.WriteLine int1とでも書けば動く。おお、いきなり.NET Frameworkがそのまま使えることの有難味が(笑) と、冗談はさておき、この「書式指定に何が使えるのか分からない」状態はひっじょーに気持ち悪いので、検索してすぐ分かるような場所に一覧が、欲しい、です。真面目にこれは挫折理由になってます。しょうがないので検索して出てきた Google BooksのExpert F#の解説を見てようやくホッとできた。

%b(bool), %s(string), %d(10進), %f(float), %O (Object.ToString()) それと%A(Any)を覚えておけば問題ない、でしょーか。ほんと予告なく%Aとか言われても困るんですよ、泣きたいですよ。%Oとの違いは、人間が見た時に良い感じに整形してくれるのが%A、でしょうか。文字列は”"で囲まれ、配列は展開して出力してくれる。

C#ではcw->TabTab->変数名、といった感じにコードスニペットを活かして手早く記述出来たわけですが、それに比べるとF#は書式指定が必要な時点で、非常にカッタルイ。カッタルイのですが、かわりに、より型に厳格です。printfn “%s” trueとか書くとコンパイル通らない。良し悪し、でしょうか。でも学習用にやってる間は面倒くさいだけですね。どうしても嫌ならば「let p x = printfn “%A” x」とでも定義しておけば良いのでしょうけれど。

gdgd言う前にF C# 言語リファレンスを見ろ、って話なのかもしれない。私は情けないことにここから書式指定を記した部分を見つけられませんでしたが。あと、選択範囲で囲んでAlt+EnterでF# Interactiveに送られるのでそれ見て確認しろって話も少しはありそう。

関数

let f x = 2*x*x - 5*x + 3
let result = f (int2 + 4)
let rec factorial n = if n=0 then 1 else n * factorial (n-1)
Func<int, int> f = x => 2 * x * x - 5 * x + 3;
var result = f(int2 + 4);
Func<int, int> factorial = null;
factorial = n => (n == 0) ? 1 : n * factorial(--n);

Tutorialには最大公約数を求めるものもありましたがfactorialと同じなので省略。F#は関数型言語ということで、やっぱ関数ですよね!キーワードはletのままで、ふつーの変数と区別なく定義できる。 C#では汎用デリゲートであるFuncとActionを使うことでそっくり再現できる。C#では型を書いてやらなければならないのだけど、F#ではより強力に推論が効くようで型の明示は不要、のようです。

再帰は、F#はlet recキーワードでそのまま書けるのに対し、C#では一度nullを代入して名前を事前に宣言しておかなければならない。というぐらいで、見た目はほとんど変わらない。そういえばifが式ですね。なのでelseは省略できないようです。else ifの連打はelifで。というわけで、このif式(?)はC#の三項演算子とほとんど同じような感じです。

let add1 x y = x + y
printfn "%A" (add1 1.0 2.0)
printfn "%A" (add1 1 2) // Compile Error
let add2 x y = x + y
printfn "%A" (add2 1 2)
printfn "%A" (add2 1.0 2.0) // Compile Error

少し脱線して型推論の話を。C#の推論は単純なだけ分かりやすくて、これは型書いてやらないといけないな、推論させるための材料を与えてあげないといけないな、というのが結構直感的だったんですが、F#だと強力な分だけ、どう推論されるのか難しい。今は漠然と、全体を見るんだなー、ぐらいにしか分かっていません。例のコードですが、add1はfloat->float->floatで、add2はint->int->intに推論されます。let add1 x y = x + yの時点ではxの型もyの型も分からないけれど、「最初に呼ばれた時に」引数の型は判明する、ということは戻り値の型も判明する。なので、その型で決定する。ということなのかなー、と。この部分はC#と全然違っていて、面白いし強力だなー、と。

Tuple

let data = (1, "fred", 3.1415)
let Swap (a, b) = (b, a)
var data = Tuple.Create(1, "hogehoge");
static Tuple<T2,T1> Swap<T1,T2>(Tuple<T1,T2> tuple)
{
    return Tuple.Create(tuple.Item2, tuple.Item1);
}

TupleはC#4.0から導入されます。F#は括弧で括るという専用記法があるので簡単に記述出来る。のに対して、C#ではふつーのclassなのでふつーにclassとして使うしかないのが残念。Swapですが、Tupleはimmutable(不変)なので、新しく生成する。だけ。です。temp用意して入れ替えて、などしない。潔く新しく作る。

Boolean, Strings

let boolean1 = false
let boolean2 = not boolean1 && (boolean1 || false)
let stringA  = "Hello"
let stringB  = stringA + " world."
var boolean1 = false;
var boolean2 = !boolean1 && (boolean1 || false);
var stringA = "Hello";
var stringB = stringA + "world.";

F#では否定が!ではなくnotなのですね。あとは一緒。

List

let listA = [ ]
let listB = [ 1; 2; 3 ]
let listC = 1 :: [2; 3]
let oneToTen = [1..10]
let squaresOfOneToTen = [ for x in 0..10 -> x*x ]
var listA = Enumerable.Empty<object>();
var listB = new[] { 1, 2, 3 }.ToList();
var listC = Enumerable.Repeat(1, 1).Concat(new[] { 2, 3 }).ToList();
var oneToTen = Enumerable.Range(1, 10 - 1 + 1).ToList();
var squaresOfOneToTen = Enumerable.Range(0, 10 - 0 + 1).Select(x => x * x).ToList();

リストを扱うとC#と大分差が出てきます。まず第一に、空リストは、C#だと該当するものは作れない。と思う。とりあえずobjectで代替することにしましたが、多分正しくありません。listBはただの整数リストなわけですが、F#だと;で区切るようです。一応、配列とリストは違うということで、C#側のコードはListにしていますがListとも違うので、まあ、気分だけ。listCの::はConsということで、一つの値とリストを連結するものです。C#に該当する関数はありません。しいていえばConcatが近いので、Repeat(value, 1)で長さ1のシーケンスを作って連結、という手を取ることにしました。

F#は[1..10]で最小値-最大値の連続したリストが作れるのですが、これはC#のEnumerable.Rangeとは、違います。Rangeの第二引数は最大値ではなく個数なので。正直言って、個数よりも最大値のほうが使いやすいと思うのだけどなー。というわけで、最大値-最小値+1 = 個数。ということにしています。最後のリスト内包表記は、うん、ええと、私は苦手です。値の動きが右行ったり左行ったりなのが嫌です。Linqのほうが好き。C#でイメージするなら、foreach (var x in [0..10]) yield return x * x; ってとこですかね。

パターンマッチ

let rec SumList xs =
    match xs with
    | []    -> 0
    | y::ys -> y + SumList ys
let listD = SumList [1; 2; 3]
Func<IEnumerable<int>, int> SumList = null;
SumList = xs => (!xs.Any())
    ? 0
    : xs.First() + SumList(xs.Skip(1));
 
var sum1 = SumList(new[] { 1, 2, 3 });
var sum2 = new [] { 1, 2, 3 }.Sum(); // こらこら

まず、listDとかF# Tutorialには書いてあるんですが、これintなのでlistじゃないでしょ!紛らわしい。さて、match with | ->という目新しい記述がパターンマッチという奴ですね? 引数のリストxs(リストは通常変数名にxsとかysとかを用いるようです)が空配列の時は0を、そうでない時はyとysに分解して、ysの方は再帰して足し合わせる。ふむぬん。C#に直すとif-else if-else ifの連打。値を返すから、三項演算子のネストですな。という程度の理解しかしていません。三項演算子ネストより綺麗に書けて素敵。という浅すぎる理解しか、今はしていません。まあ、そのうちそのうち。

y::ysという表記ですが、これは配列中の最初のものがy、それ以外がysになります。つまりLinqだとFirst()とSkip(1)ですね。let x::xs = [3..5]とすれば、xが3でxsが4,5になる。警告出ますが。基本はパターンマッチ時用ってことなのかしらん。この辺はちょっと良く分かりません。

C#のほうの、IEnumerableのままSkipをゴロゴロと繋げていくのは実行効率がアレな悪寒。かといってToArrayを毎回使うのもなあ、というわけで上手い落し所が見つからない。QuickSortのように一本の配列に対し、境界の数字を渡していくってのやるとゴチャゴチャするし。あ、でもF#のも結局ysってのはxsとは別の、新しい配列ですよね?C#で表すのならば、xs.Skip(1).ToArray()ということかしらん。だとしたら、この程度の「効率」なんて奴は、気にしたら負けだと思っている。でいいのかもしれない。よくないかもしれない。

配列・コレクション

let arr = Array.create 4 "hello"
arr.[1] <- "world"
arr.[3] <- "don"
let arrLength = arr.Length        
let front = arr.[0..2]
let lookupTable = dict [ (1, "One"); (2, "Two") ]
let oneString = lookupTable.[1]
var arr = Enumerable.Repeat("hello", 4).ToArray();
arr[1] = "world";
arr[3] = "don";
var arrLength = arr.Length;
var front = new string[3];
Array.Copy(arr, 0, front, 0, 3);
// もしくはSkip->Take. 実行効率は劣りますが、私はこちらの記述方法のほうが好き
var front2 = arr.Skip(0).Take(3).ToArray();
var lookupTable = new Dictionary<int, string> { { 1, "One" }, { 2, "Two" } };
var oneString = lookupTable[1];

配列とlistとの違い。listは不変(immutable)で、配列は可変(mutable)ということかしらん。あと配列なら.NET Frameworkのメソッド・プロパティが全部使える。mutableなものへの値の再代入は=ではなく<-で行う。あとは、Array.createは中身がnullな配列ではなく、初期値を指定して全部それで埋めるメソッドのようです。ふむ。あ、最後のslicing notationはいいですね。C#だとArray.Copyを使うのが等しいでしょうけど、記述が冗長すぎてねえ……。どうせ実行時間に対して差は出ないでしょ、と思う場合はLinqでSkip->Takeにしたほうがすっきり書けて良い。あ、あとインデクサは.[]が対応してるようです。ドット。ドット。

辞書の初期化は、タプルを放り投げるだけ。素晴らしい!見た目に分かりやすくスッキリするのがいいです。C#だとコレクション初期化子で近い形にはなりますが、{ {と、全て波括弧で記述するのはどうかなあ、と思うところがあるので。あとは一応、C# 3.0 における疑似 Map 生成リテラル - NyaRuRuの日記なんてことも出来ますけれど、やりませんものね。

関数(その2)

let Square x = x*x              
let squares1 = List.map Square [1; 2; 3; 4]
let squares2 = List.map (fun x -> x*x) [1; 2; 3; 4]
let squares3 = [1; 2; 3; 4] |> List.map (fun x -> x*x) 
let SumOfSquaresUpTo n = 
  [1..n] 
  |> List.map Square 
  |> List.sum
public static IEnumerable<TR> Map<T, TR>(this Func<T, TR> selector, IEnumerable<T> source)
{
    return source.Select(selector);
}
 
// ↑という拡張メソッドを定義して
 
Func<int, int> Square = x => x * x;
var squares1 = Map(Square, new[] { 1, 2, 3, 4 });
var squares2 = new Func<int, int>(x => x * x).Map(new[] { 1, 2, 3, 4 });
var squares3 = new[] { 1, 2, 3, 4 }.Select(x => x * x).ToArray();
// もしくは Array.ConvertAll(new[] { 1, 2, 3, 4 }, x => x * x)
Func<int, int> SumOfSquaresUpTo = n =>
    Enumerable.Range(1, n - 1)
        .Select(i => Square(i))
        .Sum();

関数が先で、それに適用する配列を渡す、という順序はC#ばかり触ってる身としては、新鮮な印象です。そういえばAchiralにも同種のオーバーロードが沢山定義されているのですが、私は違和感から、IEnumerable始点のものばかり使っています。あとSelect->ToArrayはArray.ConvertAllで書けるのですが、私はLinqで書くほうが好き。というかArrayの静的メソッドは、基本Obsoleteなぐらいの気持ちでいたりいなかったりする。

ラムダ式は「fun 引数 -> 本体」ですね。C#のほうがキーワードが必要ない分だけすっきりしてガガガ。でもnew Func<型>という不格好なものをつけなければならなかったりする悪夢。var hoge = (int x) => x * xもダメなんですよねえ。理由は、例えば「delegate int Func2(int i);」というのが定義出来るから。引数intで戻り値intだから、FuncとFunc2は同じ。でも型は違う。なので、見分けがつかず推論できないので、どのデリゲートを使うか、まで指定する必要がある。これは、悲しくウザい話です。ActionとFunc以外のデリゲート型なんて滅びてしまえばいいのに。

「|>」という見慣れない演算子が、パイプライン演算子で、左から右に値を流す。C#だと、Listに対してはLinqで、値に対しては、そういえば前に書いたような……。neue cc - ver 1.3.0.3 / ちょっとした拡張メソッド群のTapの一個目が近い感じでしょーか。いいですよね、こういうの。

Mutable

let mutable sum = 0
for i in 0..10 do
  sum <- sum + i
while sum < 100 do
  sum <- sum + 5
var sum = 0;
foreach (var i in Enumerable.Range(0, 10))
{
    sum += i;
}
while (sum < 100)
{
    sum += 5;
}

最初にF#の値はimmutableだと書きましたが、mutableにしたい時は、mutableキーワードを足せばおk。再代入時は<-演算子を使う、と。C#だとデフォルトがmutableなので、まんまです。そして、このforは、foreachですね。インデントが波括弧代わりなので、doだけどendは要りません。普通のforは「for i = 1 to 10 do」ですが、これならforeachでいいやあ、という気はする。

Types: unions

type Expr = 
  | Num of int
  | Add of Expr * Expr
  | Mul of Expr * Expr
  | Var of string
 
let rec Evaluate (env:Map<string,int>) exp = 
    match exp with
    | Num n -> n
    | Add (x,y) -> Evaluate env x + Evaluate env y
    | Mul (x,y) -> Evaluate env x * Evaluate env y
    | Var id    -> env.[id]
 
let envA = Map.of_list [ "a",1 ;
                         "b",2 ;
                         "c",3 ]
 
let expT1 = Add(Var "a",Mul(Num 2,Var "b"))
let resT1 = Evaluate envA expT1

F# Tutorialですが、ここで途端に説明が無くなって放り出されます。鬼すぎる。今までのわりとゆるふわなところから途端にコレです。意味分からないし。unionsとか言われても分けわからない。と、嘆いていても始まらないので理解するよう頑張ります。そういえば(env:Map<string,int>)も初出なのよね。推論じゃなく明示的に型を与える時は、こうするそうです。型定義がC#とは逆で、コロン後の末尾。違和感がシンドい。ActionScriptなんかも同じで非常にシンドい。

unionはC#だとenumが近いかなー、と思うのですが、enumがintのみなのに対し、F#のunionはそれぞれが別の型を持てる。といった認識。更に値は外から定義可能。というわけでenumとは全然違いますな。むしろ普通にclassに近い。of intで型を定義している(Expr * ExprはTuple)し、値は外から与えているし(コンストラクタのように!) けれど、値は一個。

じゃあclassで作れるかと言ったら、どうだろー。戻り値の型がバラバラになるので、interfaceで一個に纏められるわけでもなく上手いやり方ってあるのかしらん。パターンマッチと同じく、C#には無い概念、と素直にとらえた方が良いかも。一応、interface、じゃなくてダミーに近い型の下にぶら下げて、Evaluateのところでisで派生型を判定して分岐、といった感じでやってみましたが、ゴミですね……。

public class Expr
{
    // privateにしたいつもり(これは酷い)
    public class _Num : Expr
    {
        public int Value { get; set; }
    }
    public class _Add : Expr
    {
        public Expr E1 { get; set; }
        public Expr E2 { get; set; }
    }
    public class _Mul : Expr
    {
        public Expr E1 { get; set; }
        public Expr E2 { get; set; }
    }
    public class _Var : Expr
    {
        public string Value { get; set; }
    }
 
    private Expr() { }
 
    public static Expr Num(int value)
    {
        return new _Num { Value = value };
    }
    public static Expr Add(Expr e1, Expr e2)
    {
        return new _Add { E1 = e1, E2 = e2 };
    }
    public static Expr Mul(Expr e1, Expr e2)
    {
        return new _Mul { E1 = e1, E2 = e2 };
    }
    public static Expr Var(string value)
    {
        return new _Var { Value = value };
    }
}
 
static int Evaluate(IDictionary<string, int> env, Expr exp)
{
    return // どうしょうもなく酷い
          (exp is Expr._Num) ? ((Expr._Num)exp).Value
        : (exp is Expr._Add) ? Evaluate(env, ((Expr._Add)exp).E1) + Evaluate(env, ((Expr._Add)exp).E2)
        : (exp is Expr._Mul) ? Evaluate(env, ((Expr._Mul)exp).E1) + Evaluate(env, ((Expr._Mul)exp).E2)
        : (exp is Expr._Var) ? env[((Expr._Var)exp).Value]
        : 0;
}
 
static void Main(string[] args)
{
    var envA = new Dictionary<string, int> { { "a", 1 }, { "b", 2 }, { "c", 3 } };
    var expT1 = Expr.Add(Expr.Var("a"), Expr.Mul(Expr.Num(2), Expr.Var("b")));
    var resT1 = Evaluate(envA, expT1);
    Console.WriteLine(resT1); // 確認
}

見なかったことにしてください。私の脳みそなんてこんなもんです。

Types: records

type Card = { Name  : string;
              Phone : string;
              Ok    : bool }
 
let cardA = { Name = "Alf" ; Phone = "(206) 555-8257" ; Ok = false }
let cardB = { cardA with Phone = "(206) 555-4112"; Ok = true }
let ShowCard c = 
  c.Name + " Phone: " + c.Phone + (if not c.Ok then " (unchecked)" else "")
class Card
{
    public string Name { get; set; }
    public string Phone { get; set; }
    public bool Ok { get; set; }
 
    public Card() { }
 
    public Card(Card with)
    {
        // structならthis=withで一発なのですが
        // F#のrecordはstructじゃないとのことなので
        this.Name = with.Name;
        this.Phone = with.Phone;
        this.Ok = with.Ok;
    }
}
 
var cardA = new Card { Name = "Alf", Phone = "(206) 555-8257", Ok = false };
var cardB = new Card(cardA) { Phone = "(206) 555-4112", Ok = true };
Func<Card, string> ShowCard = c =>
    c.Name + " Phone: " + c.Phone + (!c.Ok ? " (unchecked)" : "");

こちらは割とすんなりと何なのか分かる。withでコピーが作れているところが面白い。ふーむ、C#だとむしろ匿名型のほうが近い感じに見えるかもしれない。

Types: classes

type Vector2D(dx:float, dy:float) = 
    let length = sqrt(dx*dx + dy*dy)
    member v.DX = dx
    member v.DY = dy
    member v.Length = length
    member v.Scale(k) = Vector2D(k*dx, k*dy)
class Vector2D
{
    public float DX { get; private set; }
    public float DY { get; private set; }
    public float Length { get; private set; }
    public Func<int, Vector2D> Scale { get; private set; }
 
    public Vector2D(float dx, float dy)
    {
        var length = (float)Math.Sqrt(dx * dx + dy * dy);
        this.DX = dx;
        this.DY = dy;
        this.Length = length;
        this.Scale = new Func<int, Vector2D>(k => new Vector2D(k * dx, k * dy));
    }
}

コンストラクタと定義が一体化していて、随分とシンプルに記述出来るようです。JavaScriptっぽい、なんて思ってしまったりして。C#で再現するとプロパティでメソッドかいな、という違和感があったりなかったり。private変数で蓄える必要がないから、定義が楽といえば楽。ところで思うのは、F#のv.DXとかの、vって何処から来てるの……? これ、別にhogehogeにしてもaaaaaaにしても動くので、何でもいいみたいですが……。

Types: interfaces

type IPeekPoke = 
    abstract Peek: unit -> int
    abstract Poke: int -> unit
 
type Widget(initialState:int) = 
    let mutable state = initialState
    interface IPeekPoke with 
        member x.Poke(n) = state <- state + n
        member x.Peek() = state 
    member x.HasBeenPoked = (state <> 0)
 
let widget = Widget(12) :> IPeekPoke
widget.Poke(4)
let peekResult = widget.Peek()
interface IPeekPoke
{
    int Peek();
    void Poke(int n);
}
 
class Widget : IPeekPoke
{
    private int state;
    public bool HasBeenPoked { get { return state != 0; } }
 
    public Widget(int initialState)
    {
        state = initialState;
    }
 
    public int Peek()
    {
        return state;
    }
 
    public void Poke(int n)
    {
        state = state + n;
    }
}
 
static void Main(string[] args)
{
    var widget = (IPeekPoke)new Widget(12);
    widget.Poke(4);
    var peekResult = widget.Peek();
}

interfaceはabstractな型定義を並べる。ということらしい。定義方法は「メソッド名:引数->引数->戻り値」ですねん。unitはC#でいうところのvoidみたいなもの。で、interfaceの実装は、そのまま中に記述してしまえばいいらしい。これは楽ちん。見慣れない「:>」はキャストの記号。とても、カッコイイです……。

結論

以上、複数回に分けようかとも思ったのですが一気にやってみました。最初F# Tutorialを開いて、少な!こんなんでチュートリアルになってるの?と思ったのですが、意外とギッシリ詰まってた感じです。しっかりチュートリアルになってました。ただ、やっぱチュートリアルなのでこれを覚えたぐらいじゃF#凄い!F#嬉しい!的にはなりません(比較対象がC#2.0だとなったかもしれませんが)でした。日常的に使って、手に馴染ませないと、良さの理解まではいけなさそうです。

あとまあ、やっぱほとんど説明のない、このTutorialのコードだけじゃ適当な理解になってそうで怖い。きちんと時間割いてMSDN見るなりしないと……。ただ、今のとこがっつし覚えよう!と思えてないところはある。本音として、C#でいいぢゃん、と思っているところがかなりあります。これがJava->Scalaの関係だったら違ったかもしれないんですが、うーん。まあ、あとVisualStudioの補完具合とかかな。IntelliSenseに乗ってゴリゴリ書けるような感触がF#にはないので。別に補完効いてないってわけじゃあないのですけど。

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for Developer Technologies(C#)

April 2011
|
July 2021

Twitter:@neuecc
GitHub:neuecc
ils@neue.cc