NGK2014Bで発表してきた

NGK2014Bで、FCellという製品紹介の発表をしてきました。 詳細は、発表資料よりもその中で紹介しているデモを参考にしてください*1

FCellと類似した(?)ものとしては以下のようなものがあります。

COMは置いといて、そのほかのものとFCellがどう違うかということを軽く説明しますね。

NPOIやEPPlusとの違い

NPOIやEPPlusは、基本的に「Excelを外から操る」ためのライブラリです。 こいつらはこいつらで便利なんですが、「Excelを中から操る」ことはできないので、こいつらで置き換え可能なVBAというのはほんの一部なんですね。

それに対してFCellは、ユーザ定義関数(UDF)をF#*2で定義できるのです。 そして定義したUDFは当然のようにExcel内で使えます。

Excel DNA

Excel DNAも、F#*3でUDFを定義できます。 しかし、Excel DNAは「Excelの外部」で動くものであり、Excelファイルにコードを埋め込めないです。

それに対してFCellは、Excelファイルにコードを埋め込めます。 埋め込むという選択肢のみであればVBAと変わらないのですが、埋め込まずに外に出す、ということもできます。

VSTO

VSTOは、UDFを定義する程度のものにはフォーカスしておらず、アドイン開発に特化したものという認識です。 そのため、小回りが利かないという欠点を持ちます。 また、Excelファイルにコードを埋め込むのもできないようです。

それに対してFCellは、UDFを定義する部分から、UIをカスタマイズする部分までをターゲットにしているため、非常に小回りが利きます。 F#を利用する場合であれば、Excelに統合されたF#エディタが使える*4ため、VBAと同じ感覚で使えるのが大きな利点です。

この辺りは、棲み分けの話だと思うのですが、VSTOは中規模から大規模なものに、FCellは小規模から中規模なものに使うのがいいのでは、と思います。

VSTOには詳しくないので間違ったことを言っているかもしれないです。間違っていたら指摘ください。

FCellに足りない部分

VBAの最大のメリットは、ユーザの操作を記録してそれをマクロとして使いまわしたり、生成されたコードをベースに開発したりできる点にあると思っています。 FCellは現状それができませんので、Excel利用者のものというよりはやはり開発者のためのものになっています。 今後、これが改善されてExcel利用者も気軽にFCellが使えるようになれば、と願っています。 お金の話は置いといて。

*1:ちなみに、当日はでも動画を流したのではなく、自分で作ったデモを実際に動かしました

*2:2.0からは、C#VBといった言語もサポートするようになりました

*3:もちろんC#VBも使えます

*4:補完機能やエラー表示機能も持っています

実例に見るSource変換活用術

これはF# Advent Calendar 2014の5日目の記事です。 昨日の記事はyukitosさんによるF# Project Scaffoldを使ってプロジェクトを作成するでした。 F# Project Scaffold、便利そうですね。 Chocolateyみたいに導入が簡単だと、もっといい感じになるような気がします。

さて、コンピュテーション式おじさんです、こんにちは。 最近はPersimmonというF#用のテスティングフレームワークを作っています。 Persimmon自体はまだベータ版ですが、今回はこのプロジェクトで得た知見の一つを紹介したいと思います。 誰得エントリですね。

続行可能なアサーション

Persimmonでのアサーションは、続行可能なアサーションと続行不可能なアサーションに分類されます。 例えば、

test "sample test" {
  do! assertEquals x y
  do! assertEquals a b
}

とあった時、最初のアサーションに通らなくても次のアサーションは実行できるとうれしいです。

それに対して、

let originalAssert x y = test "" {
  do! assertEquals x y
  return x
}

test "sample test" {
  let! b = originalAssert x y
  do! assertEquals a b
}

とあった時、二番目のアサーションは一番目のアサーションの結果を使っていますから、次のアサーションは実行しようがありません。

これを実現するためにはどうすればいいでしょうか?

Bindのオーバーロード

最初に考えたのは、Bindオーバーロードしてしまうというものでした。 その時に書いたのが以下のコードです。

アイディアとしては、Bindの第二引数として渡される関数(後続の処理)が必要としている型がunitかそれ以外かでBindを分けてしまう、というものです。 しかしこれは、コメントにもあるようにコンパイルエラーになってしまいます。

Sourceメソッドオーバーロードによる疑似的なBindのオーバーロードの実現

let!(やdo!)は、コンパイラによってBindメソッドに変換されますが、 ビルダーにSourceメソッドが定義されている場合、第一引数がSourceに渡されます。

Sourceがある場合のlet!の変換

T(let! p = e in ce, C) = T(ce, fun v -> C(b.Bind(b.Source(e), fun p -> v)))

Sourceオーバーロードし、中で同じ型にしてしまえばBindオーバーロードを間接的に実現できそうです。

やってみた

以下、単純化した例です。

type BindingValue<'T> =
  | UnitValue of 'T (* always unit *)
  | NonUnitValue of 'T

type SomeBuilder() =
  member __.Source(x: unit) = UnitValue x
  member __.Source(x: _) = NonUnitValue x
  member __.Bind(x: BindingValue<'T>, f) =
    match x with
    | UnitValue x ->
        (* unitの場合の処理を書く
           Persimmonの場合、アサーションに通らなくても以降の処理を続行するようなコードになっている
           アサーションの結果は、BindingValueが直接'Tを保持するのではなく、
           AssertionResult<'T>を保持することで持ちまわすようにしている *)
        assert (typeof<'T> = typeof<unit>)
        (* ここで直接()を渡してしまうと、fがunit -> 'Uのように推論されてしまうので、
           Unchecked.defaultof<'T>を使うことで回避 *)
        f Unchecked.defaultof<'T>
    | NonUnitValue x ->
        (* unit以外の場合の処理を書く
           Persimmonの場合、アサーションに通らない場合は以降の処理を続行せずに結果を返し、
           アサーションを通っていた場合は後続の処理を続行するようなコードになっている *)
        f x

このように、Sourceメソッドの引数で分岐し、戻り値をBindingValueという同じ型にするようなオーバーロードを用意することで、 Bindメソッドの中で処理を分岐できるようになります。 ちなみにreturn!を提供する場合、ReturnFromでもSourceメソッドが呼び出されるため、ReturnFromメソッドの中でBindingValueを剥いでやる必要があります。

このように、Sourceメソッドを使うとBindオーバーロードが疑似的に実現できます。

Persimmonのほかのコンピュテーション式

Persimmonでは、他にもパラメタライズテスト用のコンピュテーション式と、例外のテスト用のコンピュテーション式を用意しています。 パラメタライズドテスト用のコンピュテーション式は、シンプルなカスタムオペレーションの実装例となっています。 例外のテスト用のコンピュテーション式は、シンプルな割に便利なものになっています。 興味のある人は見てみるといいでしょう。

みなさんも、PersimmonやBasis.Coreのコンピュテーション式を参考に、 色々なコンピュテーション式を作ってみましょう!

参考URL

明日のF# Advent Calendar

日本語版はkos59125さん、英語版はSteve Shogrenさんです。 Steve Shogrenさんは「F# Polymorphism」とありますね。楽しみです!

なごやかJavaで発表してきた

なごやかJava第一回で、「.NET系開発者から見たJava」というタイトルで発表してきました。 Javaのこの機能って.NET開発者から見てどうなの?というような内容です。

大阪から参加してくれた方の感想を載せておきます。

F# 4.0プレビュー版公開!

拡張機能が使えるVisual Studioが条件付きで無償化されたことによって、 快適な開発環境でF#を学べるようになることでしょう。

それはそれで楽しみなことは間違いないのですが、今回はF#自体の使い勝手の向上に目を向けたいと思います。

昨日はVS Communityが無償提供されたほか、様々な発表がありましたが、F#も4.0のプレビュー版が公開されました。

新機能一覧

プレビュー版までで実装されたものは以下の通りです。

  • 言語に関するもの
    1. コンストラクタが第一級関数として扱われるようになった
    2. これまでrefを使わざるを得なかった場所にmutableが使えるようになった
    3. 32次元配列まで扱えるようになった(今までは4次元配列までだった)
    4. (TypeProviderで提供される)メソッドで静的パラメータが使えるようになった
    5. listで(配列と同様の)スライシングができるようになった
    6. printf系関数に測定単位が付いた値をそのまま渡せるようになった
    7. コンパイラ(fsc)のGCの設定を変更した(10%の性能向上)
  • ライブラリに関するもの
    1. コレクションモジュール(List/Array/Seq)の正規化
    2. ジェネリックな比較の高速化
    3. asyncスタックトレースの改善
    4. その他パフォーマンスの改善
  • VSツールの改善
    1. プロジェクトテンプレートへのAssemblyInfo.fsの追加
    2. F# Interactiveの起動の高速化
    3. F# Interactiveのショートカットを追加(セッションのリセット、すべてクリア)

あっと驚く新機能はないものの、全体的に使い勝手を向上させることを目的にしているように思います。 このほかの実装予定の機能については、Status of F#4.0+ Approved Language/Library Itemsを参考にしてください。

今回は、F#4.0のプレビュー版までで実装されたもの中から、コレクションモジュールの正規化を取り上げたいと思います。

コレクションモジュールの正規化

F#はList/Array/Seqという3つのコレクションモジュールを持っていましたが、 「こっちのモジュールでは提供されているけどこっちのモジュールでは提供されていない」という関数が多くありました。

F#4.0では、この状況が大幅に改善されます。 どのように改善されるのか見ていきましょう。

Seqにしかなかった関数のList / Arrayへの追加

compareWith

第一引数として渡された関数をもとに、コレクションの大小を求める関数です。

(* val List.fで、Listモジュールのf関数と思ってください *)
val List.compareWith: ('a -> 'a -> int) -> 'a list -> 'a list -> int
val Array.compareWith: ('a -> 'a -> int) -> 'a [] -> 'a [] -> int

groupBy

要素を変換した結果と、その変換のもとになったコレクションのペアのコレクションにして返す関数です。

val List.groupBy: ('a -> 'b) -> 'a list -> ('b * 'a list) list when 'b : equality
val Array.groupBy: ('a -> 'b) -> 'a [] -> ('b * 'a []) [] when 'b : equality

countBy

要素を変換した結果と、その個数のペアのコレクションにして返す関数です。 変換結果でグループ化して個数も求めるような感じですね。

val List.countBy: ('a -> 'b) -> 'a list -> ('b * int) list when 'b : equality
val Array.countBy: ('a -> 'b) -> 'a [] -> ('b * int) [] when 'b : equality

distinct / distinctBy

distinctは要素の重複を取り除く関数で、 distinctByは重複の基準を関数として渡せるdistinctです。

val List.distinct: 'a list -> 'a list when 'a : equality
val List.distinctBy: ('a -> 'b) -> 'a list -> 'a list when 'b : equality
val Array.distinct: 'a[] -> 'a [] when 'a : equality
val Array.distinctBy: ('a -> 'b) -> 'a [] -> 'a [] when 'b : equality

exactlyOnce

コレクション中に要素が一つしかなかった場合に、それを取り出す関数です。 複数の要素があった場合は例外を投げます。

val List.exactlyOnce: 'a list -> 'a
val Array.exactlyOnce: 'a [] -> 'a

last

コレクションの最後の要素を返す関数です。

val List.last: 'a list -> 'a
val Array.last: 'a [] -> 'a

pairwise / windowed

windowedは、スライド式のウィンドウを生成する関数です。 pairwiseはこの特別なバージョンで、ウィンドウのサイズが2に固定化され、ウィンドウは配列ではなくタプルとしてあらわされます。

val List.pairwise: 'a list -> ('a * 'a) list
val List.windowed: int -> 'a list -> 'a [] list
val Array.pairwise: 'a[] -> ('a * 'a) []
val Array.windowed: int -> 'a[] -> 'a [] []

singleton

渡された要素のみを含むコレクションを生成する関数です。

val List.singleton: 'a -> 'a list
val Array.singleton: 'a -> 'a []

skip / skipWhile / take / takeWhile / truncate

なぜなかった感のある関数群ですね。説明は不要でしょう。

unfold

種となる値からコレクションを生成する関数です。 これも、なぜなかったのか・・・

where

filterの別名です。これ、むしろいらんやろ・・・

Listにしかなかった関数のArray / Seqへの追加

tail

先頭要素を除くコレクションを返す関数です。

map3

3つのリストに対するmap関数です。

val Array.map3: ('a -> 'b -> 'c -> 'd) -> 'a [] -> 'b [] -> 'c [] -> 'd []
val Seq.map3: ('a -> 'b -> 'c -> 'd) -> 'a seq -> 'b seq -> 'c seq -> 'd seq

replicate

指定した個数の指定した要素を含むコレクションを返す関数です。

val Array.replicate: int -> 'a -> 'a []
val Seq.replicate: int -> 'a -> 'a seq

ListArrayにあった関数のSeqへの追加

fold2 / foldBack / foldBack2 / reduceBack / scanBack

fold系の派生関数群ですね。

iteri2 / mapi2

インデックスを伴ったiter2map2です。 今回、map3は追加されますが、Listにもともとmapi3がなかったためか、mapi3は追加されないようです。

permute

インデックスの変換関数を元に要素を置換したコレクションを返します。

val Seq.permute: (int -> int) -> 'a seq -> 'a seq

rev

シーケンスを逆順にしたシーケンスを返す関数です。

sortWith

比較関数を指定してコレクションをソートする関数です。

val Seq.sortWith: ('a -> 'a -> int) -> 'a seq -> 'a seq

ListSeqにあった関数のArrayへの追加

head

コレクションの先頭要素を返す関数です。

今までなかった関数のListArrayへの追加

splitAt

指定されたインデックスでコレクションを分割します。

val List.splitAt: int -> 'a list -> 'a list * 'a list
val Array.splitAt: int -> 'a [] -> 'a [] * 'a []

今までなかった関数のListArraySeqへの追加

contains

今まで、

xs |> List.exists ((=)x)

などとしていましたが、今後はcontainsが使えます。

findBack / findIndexBack / tryFindBack / tryFindIndexBack

find系の関数に、後ろから探すものが追加されました。

tryHead / tryLast

失敗するかもしれない関数にoptionを返すバージョンが追加されました。 tryTailが無いのは・・・?

indexed

コレクションの要素にインデックスを付けたコレクションにして返す関数です。

val List.indexed: 'a list -> (int * 'a) list
val Array.indexed: 'a [] -> (int * 'a) []
val Seq.indexed: 'a seq -> (int * 'a) seq

item / tryItem

今まで、n番目の要素を取り出す関数としてnthがありました。 しかし、これはArrayにはなく、またListSeqで引数の順番が違うというとてもアレな関数でした。

val List.nth: 'a list -> int -> 'a
val Seq.nth: int -> 'a seq -> 'a

F#4.0では、nthは非推奨となり、引数の順番が統一されたitemが提供されます。

val List.item: int -> 'a list -> 'a
val Array.item: int -> 'a [] -> 'a
val Seq.item: int -> 'a seq -> 'a

optionを返すバージョンであるtryItemも追加されました。

val List.tryItem: int -> 'a list -> 'a option
val Array.tryItem: int -> 'a [] -> 'a option
val Seq.tryItem: int -> 'a seq -> 'a option

これは朗報ですね!

mapFold / mapFoldBack

foldは種の型が戻り値の型になりましたが、mapFoldは第一引数として渡す関数の結果として次の種だけではなく追加の値も返せるようにしたことで、 mapfoldの処理を同時に行えるようになりました。 結果として返されるタプルの一つ目にmapの結果が、二つ目にfoldの結果が入ってきます。

val List.mapFold: ('b -> 'a -> 'c * 'b) -> 'b -> 'a list -> 'c list * 'b
val Array.mapFold: ('b -> 'a -> 'c * 'b) -> 'b -> 'a [] -> 'c [] * 'b
val Seq.mapFold: ('b -> 'a -> 'c * 'b) -> 'b -> 'a seq -> 'c seq * 'b

mapFoldBackfoldBackmapを同時にするバージョンです。

sortByDescending / sortDescending

逆順のソートをする関数です。 これないのつらかったんですが、ようやく入ってくれました。

VS Pro相当が無償化されたことだし、F#をやろう!

Pro相当のVisual Studioが無償になった!

とのことらしいですよ! F#*1も入っており、拡張機能も使えるようです。 ということは、素敵拡張機能であるVFPTが使えるということですよ!

もし今回のことでF#を始めるという方は、是非VFPTを導入した環境でF#を触ることをお勧めします。

そして、ついでにF# Advent Calendar 2014に参加してみるというのもいいでしょう。

みんなでF#やりましょう!

F#!F#!

追記: オープンソース開発してVisual Studio Community 2013を使おう! Visual Studio Community 2013 - Visual Studio

*1:Fortranではない

函数型なんたらの集い 2014 in Tokyo

函数型なんたらの集い 2014 in Tokyoに参加してきました。

発表資料はこちら。

内容は、Excel方眼紙を倒すためにDSLで頑張ったというものです。 この中で紹介しているTableDslは、いろいろと発展を考えているので「アレ使えばいいじゃない」は通じないのです。 あとそのアレ、Excel方眼紙吐けないじゃないですかー。

コメントで「いじられたくないならPDFにしてしまえばいいじゃない」というのがありましたが、Excelで提出することが決まっているのにそういうことできないですからね・・・

12月6日のNGK2014B 昼の部で、DSLとは違ったExcelの話をするので興味のある人はどうぞ。

F# Meetup in Tokyo

今更シリーズその2。8月3日に、F# Meetup in Tokyoに参加してきました。

発表資料はこちら。

内容は、業務でF#を使った事例の紹介です。

F#オンリーのイベントにたくさんの人が参加してくれて、またいろいろな話が聞けてよかったです。 名前出していいかわからないから伏せますけど、LangExtを使っているという声も聞けました。 あんな変態ライブラリ、自分ら以外使わんやろー、と思っていたけど、実際に使われているとわかるとうれしいものですね。

またああいうイベント、やりたい!