レッツ型推論

序文

こちらはF# Advent Calender 2015の27日目の記事です。

C#よりF#の良いところ

自分はC#で書くよりもF#で書く方が好きなのですが、その理由としてデフォルトでイミュータブルだったり、unionとパターンマッチが強力だったり、簡潔な構文だったり色々ありますが、何よりも型推論が強力なことが一番にあがります。
C#よりも型推論がかなり強力なので色々な所で型を書かずにすみます。
下のコードは意味ないですが、

let hoge ()=
  (fun choice->"hoge"),
  (fun opt->[|"fuga1","fuga2"|]),
  0.
let f1,f2,v=hoge()
printfn"%s %A" (f1<|Choice2Of2 None) (f2<|Some [|1.;2.|])

という感じにF#で書く場合は引数や戻り値で値を使えばそれで使う型を推論してくれてコードに書く必要がありません。
C#だと

private Tuple<FSharpFunc<FSharpChoice<string, FSharpOption<double>>, string>, FSharpFunc<FSharpOption<double[]>, string[]>, double> Hoge()
{
	return null;
}

と型を明記しないといけない所が多く、特にGenericを多く使おうとすると心が折れるのです…

型を書かずに済む事の楽さを実感できるのは仕様変更で戻り値や引数などを少し変えたい時です。
F#なら渡す値や戻す値を変えたら、その中身を使うところでつじつまを合わせればそれでコンパイルが通ります。戻り値の中身を気にしないでコレクションに入れておくなどのところは変更する必要もありません。
C#だと使ってるところのあっちこっちで修正しないといけなくなるので大変なのです…

型を定義しないといけないところ

そんなF#でも

  • メソッドを呼ぶところがスコープの最初に来るため推論ができない
  • レコード型やunionの型定義
  • インタフェースの定義

などでは型を明記しないといけません。

  • メソッドを呼ぶところがスコープの最初に来るため推論ができない

はそのメソッドを呼ぶのを後回しにしたり、呼ぶの自体を関数化しとくことで回避可能ですが(関数化するところはしょうがない)、で回避できなくもないですが、

  • レコード型やunionの型定義
  • インタフェースの定義

定義なので書かざるを得ず、苦々しく思っていました。
いくら多少簡潔に書けるといっても

type HogeUnion=
  |Fuga1 of (Choice<string,double option>->string)*(double[] option->string)*double
type IHogeInterface=
  abstract Fuga:(Choice<string,double option>->string)*(double[] option->string)*double->unit

というようなのを書くのはつらたんです。

Statically Resolved Type Parameters

そんなことを常々思ってつぶやいてたら@haxeさんがStatically Resolved Type Parametersのことを思い起こしてくれました。
inlineの時とか使ってたけど他ではそういえば使ってなかったかも。

とりあえず試してみる…

  let hoge ()=
    (function
      |Choice1Of2 v->sprintf "%s" v
      |Choice2Of2 (Some v)->sprintf "%f" v),
    (fun opt->"fuga1"),
    0.
  let useHoge p=
    let f1,f2,v=p
    sprintf"%s %s %f" (f1<|Choice2Of2 (Some 0.)) (f2<|Some [|1.;2.|]) v|>ignore
    //下の行をコメントアウトするとFuga使うところでエラー
    sprintf"%s %s %f" (f1<|Choice1Of2 "") (f2<|Some [|1.;2.|]) v|>ignore

  type HogeUnion2< ^a>=
    |Fuga2 of ^a
  type IHogeInterface2=
    abstract Fuga: ^a-> ^b
  type Hoge2()=
    interface IHogeInterface2 with
      member x.Fuga p=useHoge p
  let hoge2=Hoge2()
  let fuga=Fuga2<|hoge()
  match fuga with
  |Fuga2 p->useHoge p
  (hoge2:>IHogeInterface2).Fuga<|hoge()

できた…できたよパトラッシュ(´・ω・`)

まとめ

これ使うと型定義のところにつらつら型を書かなくてもよくなります。
ちゃんと型を使い切ってないとエラーだされたり、たまにどこでずれてるかわからずアノテーションを追加することもあるけれど、それでも型を書かずに済むのは便利です。
型推論サイコー。
これからも型推論を使ってずぼらにコードを書いていきたいと思います(´・ω・`)