fluent feature in f#
DESCRIPTION
from OCaml Meeting 2010 in NagoyaTRANSCRIPT
F# の流技Fluent Feature in F#
by いげ太
あいさつ。自己紹介。
本田△
流技といえば本田。無回転シュート。空気の流れを利用。大活躍!ネットでよくこんなのを見た。「本田△」は前へ前へ攻めていく感じ?じゃなく、本田△⇒本田三角形⇒本田さんかっけー⇒本田さんカッコイイ。
OCaml△
ていうか、今日は OCaml Meeting なわけで。OCaml さんかっけー。って言いたかったけど、僕は Windows しか知らないので。
F# △
似た言語である F# について話したい。 F# は、かんたんには OCaml.NET です。ていうか。△似合わない? サッカーならいい。アゲアゲで、敵陣に攻めてる感じ。プログラミング言語で、上って? ソース コードの上の方? それ後ろ向きじゃん。
前向きにいきましょう。矢印は未来を指してないと。F# には横向きの三角、というか、矢印がよく似合います。でもこれ、縦書き文字で無理やり横向きの三角を出してますので。
F#
F# |>
こうしましょう。パイプと大なりであらわす。この横向きの矢印は、 F# ではパイプライン演算子と呼ばれます。パイプライン演算子は、ライブラリ標準の演算子だったりします。
パイプライン演算子 |>
h (g (f x))↓
x |> f |> g |> h
ネストいらずって素敵やん!?
関数適用のネストをなくして見やすくする演算子。補助的な演算子。前から順番に、 x を f に適用して、その返り値を g に適用して、またその返り値を h に適用して、その結果を得る。わかりやすい!
パイプライン演算子?
let (|>) x f = f x
let wup n = 2 * nlet ans = 2 |> wup |> wup |> wup
// val ans : int = 16
(|>) は、前向きなパイプライン演算子、前方パイプ演算子。定義はこんな風になっている。ちょっとした例も。ダブルアップで倍々ゲーム。でもさ、若干だけど、重くなるんじゃない? 無駄じゃない、こんなの?
パイプライン演算子!
let inline (|>) x f = f x
let wup n = 2 * nlet ans = 2 |> wup |> wup |> wup// let ans = wup (wup (wup 2))
// val ans : int = 16
(|>) は、実際にはインライン関数(演算子)として定義されています。インライン展開されるので実行時のコストはゼロ。気兼ねなく、存分に使ってください。乱用バッチコイ。
後ろから前から
h @@ g @@ f @@ x
h (g (f x))
x |> f |> g |> h
関数型の語順
でもさ、関数型コードとの親和性で言えば @@ の方がいいんでない?
F# は .NET 言語であり、当然 OOP の機能を備える。.NET 使いも納得の思考の流れでコードが記述できる。
.NET といえばオブジェクト指向ですよ
h @@ g @@ f @@ x
x.F().G().H()
x |> f |> g |> h OOP の語順
ところで、 F# Team の一人である Brian McNamara が、stackoverflow のとあるスレッドでこんなことを書いていました。要は、ざっくり言うと、前方パイプ演算子は文化だ、と。
前方パイプ演算子は文化だ
Brian McNamara writes,
“I think (|>) is an important operator in the F# ‘culture’.”
http://stackoverflow.com/questions/1457140/
文化ですってよ。
One more thingOne more thing...
<censored>
ドット記法の語順とあわせ打っただけをもって「文化」と言っているわけじゃあない。
<censored>
だけじゃないOne more thing...
だけじゃない!!
<censored>
F# 「 OCaml と違うんです」
(* OCaml *)let n = 2 + 3 (* 5 *)let m = 2.0 +. 3.0 (* 5.0 *)
// F#let n = 2 + 3 // 5let m = 2.0 + 3.0 // 5.0
たとえば、 .NET には演算子オーバーロードがある。型推論の完全性を重んじる OCaml とは一線を画す。
オーバーロードと型推論
let func1 x = System.Console.WriteLine(x) // error FS0041 x + 1
let func2 (x: int) = System.Console.WriteLine(x) x + 1let func3 x = let y = x + 1 System.Console.WriteLine(x) ylet func4 x = printfn "%A" x x + 1
まあ割と型推論と相性が悪いのですけどもー。型推論ってば、評価時の型環境すべてをひっくるめて見てくれる。でも、オーバーロード解決は、その場その場で行われる。
F# 「 .NET ですから」
• OCaml は Structural Subtyping• OCaml には Row Polymorphism もある• F# は Nominal Subtyping• .NET 的に考えて Nominal でなきゃマズ
イ
⇒ F# の型推論は残念!?
で、オブジェクト指向の方向性としても、こんな風に違うわけで。ちょっと難しいので突っ込んで話はしませんが。
デモ> String.length "hoge";;val it : int = 4> "hoge".Length;;val it : int = 4> String.length;;val it : (string -> int) = <fun:it@3>> (fun s -> s.Length);;
(fun s -> s.Length);; ----------^^^^^^^^
stdin(4,11): error FS0072: このプログラムの場所の前方にある情報に基づく不確定の型のオブジェクトに対する参照です。場合によっては、オブジェクトの型を制約する型の注釈がこのプログラムの場所の前に必要です。この操作で参照が解決される可能性があります。> (fun (s: string) -> s.Length);;val it : string -> int = <fun:clo@5>
とりあえず、 OCaml 的に考えて、どんなふうに残念かってーのをデモで見ます。ドット記法で書かれたオブジェクトの型は推論されない!型注釈に頼らざるを得ない。
デモ> let intLength n =- (fun (s: string) -> s.Length)- ( (fun n -> n.ToString()) n );;
val intLength : 'a -> int
> let intLength n =- (fun (s: string) -> s.Length)- ( (fun (n: int) -> n.ToString()) n );;
val intLength : int -> int
> let intLength (n: int) =- n |> (fun n -> n.ToString()) |> (fun s -> s.Length);;
val intLength : int -> int
ちょっと意図的な例。それぞれの引数に型注釈付けるのは煩雑極まりない。なんとかならない? それまでに型が決まればいい。 F# の型決定は左から右。なので、前方パイプ演算子を使えば、残念さをすこしはカバーできるかも!?
まとめ
> F#- |> 関数型ベース- |> .NET 言語ですが何か- |> OOP は必須です- |> Nominal だし型注釈を免れない- |> 型推論を最大限活かしたい- ;;
val it : culture = だまって前方パイプ書けよ
More Feature
• 単位系 → 数値に単位を付けて計算
• アクティブ パターン → パターンとして使用できる関数
• オブジェクト式 → 無名クラス
• コンピュテーション式 → モナド
• イベント ドリブン → リアクティブに記述可能
• 非同期実行 → 非同期ワークフロー、アクター
他にもおもしろいのいっぱいあるよー。
Reserved as Keywordsfor future expansion
atomic break checked component
const constraint constructor
continue
eager event external fixed
functor include method mixin
object parallel process protected
pure sealed tailcall trait
virtual volatile
http://msdn.microsoft.com/en-us/library/dd233249.aspx
わくわくどきどきの将来の機能拡張のための予約語。
F#F is for Fun!
ありがとうございました。