F# Asyncに関しての落書き その1

調べてる最中の落書きなので、指摘ある場合はどんどんお願いします。

コードを読むための前提知識(たぶん)

  • .NETの非同期
  • 継続
  • Trampoline

ソースコード

今読んでるやつ

https://github.com/fsharp/fsharp/blob/6ac6318858da25fe4f1d4728901c5824e9f3bf3a/src/fsharp/FSharp.Core/control.fs

FakeUnitValue

https://github.com/fsharp/fsharp/blob/6ac6318858da25fe4f1d4728901c5824e9f3bf3a/src/fsharp/FSharp.Core/control.fs#L383

ここはコメントの通り、と。

Trampoline

内部でのAsync専用Trampolineの実装に.NETの限界を垣間見る

do!とか

Async in C# and F#: Asynchronous gotchas in C# (Japanese translation) に"末尾再帰関数では do! より return! を使うべき"とか書いてあった気がする。do! の変換を見てみよう。

詳説コンピュテーション式 - ぐるぐる~

上記でも解説されている通り、do!には二つの変換パターンが存在する。

T(do! e in ce, C) = T(let! () = e      in ce,         C)
T(do! e;,      C) = T(let! () = src(e) in b.Return(), C)

末尾再帰関数として使う場合に使うのは後者。Bindが適用されてからReturnする影響でリークする可能性があるのかな?(実装を全部読む時間が足りない…)まぁ、無駄な呼び出しはしないほうが良いのは確かだ。

Asyncとは関係ないが、let!でもSource適用するのになんでdo!にもつけたのだろう。それも片方の変換だけ…

Async.Catch

コンピュテーション式内でtry withを使うのとAsync.Catchで包むの、どちらが良いのだろう?え、Choice使いたくない?まぁ、はい。

overload

https://github.com/fsharp/fsharp/blob/1745b5f7b19bdb285f3a9c06527b22baf01d41f9/tests/fsharp/typecheck/sigs/pos12.fs

テストで思い切りビルダーに対して拡張メソッドを定義している…わりとよくやるのだろうか。というか、F# 3.0以降なら

type Microsoft.FSharp.Control.AsyncBuilder with
    member x.Source(computation:Task<'T>) = Async.AwaitTask computation

でいいのでは感。どうせなら次のバージョンで標準搭載されてほしい。


会社に遅刻するので、今日はここまで。

Microsoft MVP for F# を再受賞しました

再受賞しました。今年は無理だろうと思っていたので驚いています。これも私にF#を愛でるチャンスをくださり、後押ししてくださった皆様のおかげです。ありがとうございました!今後とも、よろしくお願いします。

去年は何かを作る方向でゆるふわできなかったので、今年は作るほうでゆるふわできたらと考えています。

F#!F#!

社会人になってから1年経過して思ったことなど

詳しく書くほどでもないと思うので箇条書き。

  • 多少日本語になっていない会話でも、なんとかならなくはないのかもしれない
  • たまには外の空気を吸うべき(家と会社の往復のみは思考停止する)
  • コード書く"毎日"は、それはそれで生きていると実感できる
  • 仕事は選べる"かもしれない"
  • 定時帰宅を続けると遅刻回数が減る
  • いざというときに行動するためには、それなりの資金がいる(あたりまえだけど、あらためて)
  • 帰宅しないという選択肢
  • C++とBoostは、これはこれで面白い
  • F#!
  • あだ名重要説
  • SQLアンチパターンは読むべき(背景:この職業、SQLから逃れるのは厳しいのではないか)
  • つい昔話してしまう…これが老いか
  • ノリと勢い、それと運
  • 逆に考えるんだ、「進捗しなくてもいいじゃない」ってさ
  • 夜行バスからの出社は仕事にならない
  • 勉強会で「新卒です」って言ったら驚かれたことがあったのはなんでなんだぜ?

逃避行終わり。資料を書こう…。

パーサコンビネータを使って簡単なNGワードフィルタリング機能を作る

昔、 RSpec の入門とその一歩先へ - t-wadaの日記 を読んで「自分だったらどうつくるかなー」と考えていた。

そして時が経ち、パーサコンビネータを知った今となっては、簡単なものであればこれでいいんじゃないかと思っている。

というわけで、以下は F# の ParsecClone というライブラリを使った例。

フィルタリング対象の文字列を発見する

利用者が指定したワードにマッチするようにすればよい。

// cutting : string -> string
// word: NGワード
let dirtyToTurn cutting word = matchStr word |>> cutting

マッチしたら伏せ字に入れ替える関数を適用すれば、それらしいものになる。

NGワードを複数登録できるようにする

NGワードリスト内のどれかにマッチするようにする。

// words: NGワードリスト
let dirtyToTurn cutting words = anyOf matchStr word |>> cutting

それ以外の文字列

任意の文字列にマッチすれば良い。ParsecClone の場合は any 関数が合致する。

文章を構成する

文章は、NGワードとそれ以外の文字列が0個以上組み合わさっている、と考えられる。

let parser cutting dirtyWords =
  many (dirtyToTurn cutting dirtyWords <|> aby) >>= foldStrings

foldStrings は ParsecClone に存在する関数で、parse した文字列のリストを連結してくれる Parser。

NGワードが含まれているか判定したい

巷のパーサコンビネータライブラリは状態を持つことができるような仕組みを提供していることが多い。ParsecClone にも存在する。

let dirtyToTurn cutting words =
  anyOf matchStr word |>> cutting .>> setUserState true

let parser cutting dirtyWords =
  many (dirtyToTurn cutting dirtyWords <|> aby)
  >>= foldStrings
  .>>. getUserState

これで、最終結果としてNGワードが存在するかどうかとフィルタリング結果が取得できるようになる。

他にも色々やりたい

全角半角を区別せずにフィルタリングしたいとか、でもフィルタリング対象以外の文字列はきちんと復元されてほしいとか色々あるなら、もう少し実装を考える必要がある。

ソースコード

今回のコードは以下においている。

https://github.com/pocketberserker/Harvester

名前の由来はそのうち書く。

他の言語でできるの?

経験から、少なくとも Boost.Spirit(Qi, Karma)はこの手法で実装できる。 あと、割りと新顔の ParsecClone でもできるので、他のライブラリでもできるのではないかなとは思っている。

F# 用非公式 MessagePack ライブラリを作ってみている

F# 特化型です。

ソースコード

公式

公式は CLI 向けの msgpack-cli が存在します。

なぜ作ったの?

公式のものが F# 向きかと言われるとうーんと思い、なら勉強がてら趣味開発に利用しようというのと、公式の F#ラッパーを書く体力がなかったことが発端です。品質などを考えると、公式をラップしたほうが良いというのはわかっているのですが…。

現状

  • pack するためには MsgPackValue<'T when 'T :> comparison> という判別共用体に落としこむ必要あり
  • comparison なのは Map の影響
  • ext を任意の型にしたい場合は packExt<'T when 'T :> IPackable, comparison>, unpackExt<'T when 'T :> comparison>
  • ext を byte [] のまま保持するなら pack, unpack
  • 任意の型にしない場合、もしくは ext を使わない場合は MsgPackValue 型を用いる
  • tyep MsgPackValue = MsgPackValue;; (pack の関係で Unitが使えなかった…)
  • MsgPackValue を簡単に生成するための Limited モジュール
  • 旧仕様は OldSpec モジュールで提供
  • テストは基本的に FsCheck (array32 と map32 の重さに耐えられなかったけど!)
  • unpack の内部実装は PasecClone
  • 実行速度は後で考える

そのうちやりたい

  • 性能検査
  • シグネチャ調整
  • README含むドキュメント整備
  • Type Provider実装
  • Mono 対応 (Mono 3.2.7 だと F# 3.0 にバージョンを下げないといけない…etc)
  • Xamarin 系対応
  • パフォーマンス・チューニング
  • NuGet公開

「御託はいいからさっさと NuGet に公開しろ!」とか言われたら、最優先で対処します。

他にこれほしい、というものあれば twitter なり issue なり pull request なりでお願いします。ユーザ自分しかいなさそうですけどっ!

F# プロジェクトが利用できそうな外部CIサービスについてのメモ

タイトル通りだけど無料版っぽいものがあるもののみ対象。候補は4つ。

Travis

http://travis-ci.com/

みんな大好き Travis ちゃん。 どうやって動かすのかと思いきや、なんとLinux系ならapt-get、Macならmonoをwgetしてインストールするという荒業を使う。以下Macな環境を対象とした .travis.yml の例。

language: objective-c

env:
  matrix:
    - MONO_VERSION="3.2.7"

install:
  - wget "http://download.xamarin.com/MonoFrameworkMDK/Macx86/MonoFramework-MDK-${MONO_VERSION}.macos10.xamarin.x86.pkg"
  - sudo installer -pkg "MonoFramework-MDK-${MONO_VERSION}.macos10.xamarin.x86.pkg" -target /

script:
  - お好みにあわせて記述

Monoでテストが通るか試せるのは大きいけど、いいのかこれ…? mono 3.2.7 だとまだ FSharp.Core 4.3.1.0 は同梱されていないんじゃないかなー。あと色々面倒くさいので FAKE を併用したほうがよさげ。 FAKE 使いたくないのだけどなー。

利用実績

TeamCity

http://www.jetbrains.com/teamcity/

最初に"無料版っぽいもの"と書いた理由がこの人。 活発でウェブサイトを持っている、活動が活発なコミュニティの、フリーで非営利な開発プロジェクトについて(このあたり英訳あやしいので間違っていたらすみません)Open Source Licenseがあったりする。あくまでコミュニティ用。 使ったことがないのでこれ以上の言及は避ける。

利用実績

  • fsharp

AppVeyor

[2014/03/18 URL修正]

http://www.appveyor.com/

.NETに特化したようなCIサービス。特化しているだけあって、準備がラクダ。 ほとんど設定せずとも F# 3.1 で VS2013 なプロジェクトがビルド & テストできる。試してないけどデプロイもできるようだ。

利用実績

[2014/03/19 Visual F# Power Toolsを追加. id:bleis-tift さん、情報提供ありがとうございます。]

Visual Studio Online

http://www.visualstudio.com/products/visual-studio-online-basic-vs

コラボレーション(?)サービスなので、正確にはCIサービスではないけど一応。BasicだとExpressも対象に含められる。 "Basic プランでの最初の 5 人のユーザーと、資格のあるすべての MSDN サブスクライバー (Visual Studio Professional with MSDN 以上) "という記述の通りである。 F# 3.1 は試したことがないけど、たぶん大丈夫なのではー。

利用実績

自作ライブラリで試したことくらいしかないので、実績求む。

感想

用途にあわせて選べば良いかと。

F# でCPS版List.foldを作ってみよう

自分用メモ。

まず fold を作る

よく知られる List.fold の定義は、わかりやすいものであれば以下の様な形。

let rec fold' f acc = function
| [] -> acc
| x::xs -> fold' f (f acc x) xs

これをCPS(継続渡しスタイル)に書き換えると

// ('a -> 'b) -> (('a -> 'b) -> 'a -> 'c -> 'b) -> 'a -> 'c list -> 'b
let rec foldk k f acc = function
| [] -> k acc
| x::xs -> f (fun v -> foldk k f v xs) acc x

おそらくこんな感じ。使ってみる。

> foldk id (fun k acc x -> acc + x |> k) 0 [1..5];;
val it : int = 15

> foldk id (fun k acc x -> acc * x |> k) 1 [1..10];;
val it : int = 3628800

一番目の処理を展開してみよう。

foldk id (fun k acc x -> acc + x |> k) 0 [1..5]
(fun k acc x -> acc + x |> k) (fun v -> foldk id (fun k acc x -> acc + x |> k) v [2..5]) 0 1
0 + 1 |> (fun v -> foldk id (fun k acc x -> acc + x |> k) v [2..5])
foldk id (fun k acc x -> acc + x |> k) 1 [2..5]
(fun k acc x -> acc + x |> k) (fun v -> foldk id (fun k acc x -> acc + x |> k) v [3..5]) 1 2
1 + 2 |> (fun v -> foldk id (fun k acc x -> acc + x |> k) v [3..5])
foldk id (fun k acc x -> acc + x |> k) 3 [3..5]
(fun k acc x -> acc + x |> k) (fun v -> foldk id (fun k acc x -> acc + x |> k) v [4..5]) 3 3
3 + 3 |> (fun v -> foldk id (fun k acc x -> acc + x |> k) v [4..5])
foldk id (fun k acc x -> acc + x |> k) 6 [4..5]
(fun k acc x -> acc + x |> k) (fun v -> foldk id (fun k acc x -> acc + x |> k) v [5]) 6 4
6 + 4 |> (fun v -> foldk id (fun k acc x -> acc + x |> k) v [5])
foldk id (fun k acc x -> acc + x |> k) 10 [5]
(fun k acc x -> acc + x |> k) (fun v -> foldk id (fun k acc x -> acc + x |> k) v []) 10 5
10 + 5 |> (fun v -> foldk id (fun k acc x -> acc + x |> k) v [])
foldk id (fun k acc x -> acc + x |> k) 15 []
id 15
15

うん、良い感じ。ただ、継続が最後の引数になるようにしたほうがよいかもしれないと思ったり。

他の関数を定義してみる

簡単な sum から。

let sum k xs = foldk k (fun k x y -> x + y |> k) 0 xs

> sum id [1;2;3];;
val it : int = 6

> sum (~-) [1;2;3];;
val it : int = -6

次に map

let map k f xs = foldk k (fun k xs x -> seq { yield! xs; yield f x } |> Seq.toList |> k) [] xs

> map id ((*) 2) [1..5];;
val it : int list = [2; 4; 6; 8; 10]

続きはまたそのうち。