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#をやろう!

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

F#

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

発表資料はこちら。

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

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

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

F# Meetup in Tokyo

F#

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

発表資料はこちら。

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

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

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

クラウド温泉4.0@小樽 - The Return of F#

F#

今更シリーズその1。 7月の25から28日に、 クラウド温泉4.0@小樽 - The Return of F#に参加するために北海道に行ってきました。

発表資料はこちら。

内容はコンピュテーション式のyieldとreturnについての話で、

あたりの流れの一つのまとめになっています。 上であげた、「コンピュテーション式におけるreturnとyield」では状態引数による実装のみを紹介していましたが、 今回の発表資料の中では、

  • 例外による実装
  • 状態変数による実装
  • 状態引数による実装
  • 継続による実装

と、多数の実装方法を紹介しています。

また、FSharpxやExtCoreといったF#の有名ライブラリが、コンピュテーション式を正しく実装できていない点(yieldやreturn以前の問題)も明らかにしました。 詳しくは追っていませんが、実は標準のAsyncコンピュテーション式がそもそもダメという話も・・・ そもそも、コンピュテーション式を展開して考えている実装者はおそらく皆無であり、 ネット上にある間違った情報をもとにコンピュテーション式を実装している場合が多いように思います。 モナドモナドプラス以上の表現力のものを実装する場合は、せめてコンピュテーション式の展開規則を理解したうえで実装しましょう。

沖縄に行ってきました!

etc

社員旅行で沖縄に行ってきたので、その報告です。

f:id:bleis-tift:20140924093102j:plain

http://f.st-hatena.com/images/fotolife/b/bleis-tift/20140924/20140924093102_original.jpg

お分かりいただけただろうか?

f:id:bleis-tift:20140924093128p:plain

_人人人人人人人人人人人人人_
> Javaプログラミング講座 <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

報告は以上です。

Ruby嫌いがアンダースタンディングコンピュテーションを読んで

一番最初にはっきりさせておきますが、Rubyは嫌いな言語です。 が、この本はRubyが嫌いな自分でもいい本だと言える*1本でした。 自分が対象読者に入っているかどうかは実際に読んでみるまで微妙かな、と思っていましたが、とても楽しめました。 以下、書評です。

Rubyという選択

説明用のコードとして本書はRubyを使っていますが、 これに関してはその理由が1章にあります。

私はその明瞭さと柔軟さに魅かれてRubyを選びました

また、続けて

本書にはRuby独自の機能に依存しているところはありません。 そのため、もしあなたにとってわかりやすくなるなら、本書のサンプルコードをお好みの言語、特にPythonJavaScriptといった動的言語に変換してもよいでしょう。

とあります。

ここで注目すべきは「特にPythonJavaScriptといった動的言語」という風に、静的型付き言語を省いている(少なくとも積極的に推奨はしていない)点です。 で、これにはまっとうな理由がある(少なくとも自分は納得できる)のですが、この説明だけだとそれが全然わからないんですよね。 その理由をここに書いてしまうのは、ある意味この本を読む楽しさの一部を削いでしまうことになりかねない気もするのですが、そうなったとしてもこの本は依然として面白いであろうということで、書きます。 もちろん、「こういう理由があるからだ」という風に書きますが、すべて想像です。

対話環境があり、モンキーパッチングが可能であり、定数が削除できる

この本の素晴らしい点の一つに、実際に対話環境で一つ一つコードを入力していき、動作が確認できるというのがあります。 対話環境がない言語の場合はいちいち実行し直す必要があり、しかもこれまでに見てきた最早不要の出力(ようはゴミ)も表示されてしまいます。 面倒に思うかもしれませんが、きちんと理解したい方はRubyの環境を整えて*2、実際に対話環境でコードを入力して結果を確認しつつ読み進めるのがいいでしょう。

また対話環境前提で構成されているため、「以前定義したクラスのコードに戻ってこのメソッドを追加しましょう」ということができません。 そのため、モンキーパッチングができるような言語でないと実際に試しながら読むのはつらいです。

更には、「以前定義したクラスのコードに戻ってこの部分を書き換えましょう」もできません。 なので、定数を削除(というより、クラスを削除)できる必要があるのです。

もちろん、モンキーパッチングの代わりに別のモジュール内に既存のモジュールと同じものを定義してそれをopenするだとか、定数の削除の代わりにシャドウイングを使う等、このあたりはこの機能そのものがないと厳しいというわけではありません。

evalを持つ

eval的な、渡されたコードを実行する仕組みが欲しくなる場所があります。 本書で変換対象として挙げられていた言語(や、動的言語の多く?)は、eval的なものが用意されています。 これが気軽に使える言語でないと、evalを自分で実装・・・ということになりかねません。 すると当然のことながらすべてを本文に載せることは出来なくなり、本文のコードで試せないものができてしまいます。

もちろんこれにも回避方法はありますが、説明をシンプルに保つ、という点において本書がRubyのような言語を採用したのは正解だったと思います。

ちなみに、ここで挙げた理由を持つPythonJavaScriptが本書の言語として選ばれなかった理由としては、著者の好みもあるでしょうが、一番の理由はP.188からP.191な気がしています。

クラス分割の教材という観点

Rubyメタプログラミング的な技法などを使えば本書のコードはよりDRYに書けるでしょう。 ですが、それをすることなく一般的なOOPLで扱える程度の記述にとどめているため、他のOOPLでもクラス分割のひとつのお手本としてみることができます。

そういう観点でのオススメは3章です。 3章をすべてやると(正確には3.3まで)、基本的な機能をもった正規表現の処理系が手に入ります。 正規表現の処理系を「じゃぁ作って」と言われても、難しいと感じるプログラマは多いと思います。 3章では、それをボトムアップで作っていきます。 この際のクラスの分割方法や命名のセンスは素晴らしいです*3

3章の動機づけの弱さ

3章はコードは素晴らしいのですが、構成にもったいなさを感じました。 最終的には基本的な機能を持った正規表現の処理系が手に入るにもかかわらず、導入部分には

計算する機械というアイディアにある本質を明らかにし、それがどんな用途に使えるのか紹介しながら、単純なコンピュータにできることの制限について調べます。

としかないのです。 3章の最初の方はこの本の対象読者にとって「これが一体何に使えるんだ・・・」という感じの話が続きます。 人によっては退屈に感じて途中でやめてしまうかもしれません。 なので、個人的には最初の方に「この章では単純な例から始め、基本的な正規表現の処理系を作り、単純なコンピュータでも役に立つことを示します」くらいの人参をぶら下げた方がよかったのではないかな、と思います。

6章のバランス

この本は非常に内容の詰まった本であり、技術書の中ではページ数も少な目か普通程度のものです。 これだけの内容をこれだけ分かりやすくこれだけのページ数に収めているのは驚異的だと思うのですが、6章は少しアンバランスな気がしました。 もう数ページ増やして、リストの説明をより詳細に行ってもよかったのではないかな、と思うのです。 これまで丁寧に解説をしていたのに、リストでは解説なしに5つの関数が与えられてあとはその動作の確認にとどまっています。 本書のもったいないと思ったところで一番大きな部分が(言語の話ではなく)ここでした。

日本語としての読みやすさ

とても読みやすかったです。 「的」をあまり使わない方針なのか、一部ひっかかりを覚えた部分もありますが、好みの問題でしょう。 何か所か原文を確認したくなったところはありましたが、とても少ないです。 意味がよく分からないな、と思った個所は一つだけです。 P.169に、

ブール値を将来のコードが読める決まったデータとして考えるのではなく、2つの選択肢を引数として呼び出し、1番目と2番目の選択肢のどちらかを選ぶコードとして、直接的に実装しましょう。

とありますが、この前半部分の意味が分かりませんでした。 ただ、ここが分からなくても後半部分だけで実際にやりたいことはわかるため、それほど問題とも思いませんでした。

本書を読むために必要なレベル

人は自分がわかっていることに対してそのレベルを低く設定しがちな気がします。 「わかっている人にはわかる説明」というのはいくつもあり、分かってしまえばその説明で確かに十分だ、と思ってしまうことはそれなりにあります。

自分はある程度知識がある状態でこの本を読んでいるため、本書を読むために必要なレベルを本来よりも低く見積もってしまう可能性があります。 という言い訳を置いたうえで、この本を読むために必要なレベルはそこまで高くないと感じています。 ただし、最初の方にも書きましたが「実際にコードを試しながら読む」のを前提として、です。 逆に、実際に試さずに「難しかった」というのは、この本の読み方としては難易度の高い方法を選んだからかもしれませんよ?

汚れ?

P.210ページの右下付近、「partial」の「a」の上にななめ線が入っていました。 自分の周りの3冊を確認したところ、3冊すべてで入っていたので、多くの本で同じようなものが入っているでしょう。 内容には関係ない話なのでこれがあるからと言って本書の価値が下がるわけではありませんが。 むしろ、将来直った場合には自慢ポイントになるかも?

全体

全体を通して、「そういう説明をするのか、なるほど」と感じた個所が多い本でした。 これまで理解できていなかった部分が理解できるようになったり、新たな知識(薀蓄的なもの)が知れたりして勉強にもなりました。 Ruby嫌いは直りそうにありませんが、この本は好きになりました。

追記

サポートページはGithubなんですね。 そして、Issue 1に登録されていました。

次に読む本Wikiとしてまとめられているのも素晴らしいです。

*1:Rubyを使っているというだけですでにマイナスポイントからのスタートであるにも関わらず、いい評価

*2:好きな言語にコンバートしつつ、でもいいでしょうが、その場合でも本文を読み飛ばさずに進めた方が楽しいと思います。

*3:ただし、正規表現の処理系としてのクラス分割や名前付けが素晴らしい、という話ではないです