Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

F#で遊んでみる(6)

今回は、この本の5.10から進める。

5.10はパターンマッチ。

パターンマッチはRustにもあるし、最近はRubyにも入ったけど、しっかり理解出来たとは言えない。 よく使うイディオムは書けるんだけど、それがどういう仕組みで動いているのかがよくわからない。 例えば、構造でマッチするパターンと、値でマッチするパターンがあって、それが組み合わされたりするけど、 あーゆーのをどういうルールで処理系が理解しているのかって、しっくりは来ていない。

だけど、まずはいろんな言語でいろんな本を読んでみる(そうして、帰納的な理解を深める)しかなかろうと思ってるので、今回もやっていく。

ここで紹介されているパターンは以下。他にもまだまだあるよとのこと。うーむ。

  • タプルパターン
  • 定数パターン
  • 変数パターン
  • ワイルドカードパターン
  • ORパターン
  • asパターン

ORパターンはパターンを並べる|とmatch式の|で同じ記号を使っているのが面白いと思う。これは好きかも。

> let f c = 
-     match c with
-     | 'a' | 'i' | 'u' | 'e' | 'o' -> "Japanese Vowels"
-     | _ -> "Others";;
val f: c: char -> string

> f 'a';;
val it: string = "Japanese Vowels"

> f 'b';;
val it: string = "Others"

さて、続きだ

  • function式
    • 引数を1つだけ取って、その引数に対してmatchさせる関数
  • レコード(type)
    • レコードに対する型推論は(フィールドの型ではなく)フィールド名を使って行われる
    • レコードパターン

レコードのコピーと更新はこんな感じで出来る。普通に作るとレコードはimmutableなので、更新と言っても新しいのが出来る奴ね。

> type Player = { Name: string; Job: string; Level: int};;
type Player =
  {
    Name: string
    Job: string
    Level: int
  }

> let p1 = { Name = "Bill"; Job = "Wizard"; Level = 5};;
val p1: Player = { Name = "Bill"
                   Job = "Wizard"
                   Level = 5 }

> let p2 = { p1 with Job = "Knight"; Level = p1.Level + 1};;
val p2: Player = { Name = "Bill"
                   Job = "Knight"
                   Level = 6 }

フィールドをmutableにすることも出来る。もちろん、望ましくはない。

> type Player = {Name: string; mutable Job: string; mutable Level: int};;
type Player =
  {
    Name: string
    mutable Job: string
    mutable Level: int
  }

> let p1 = {Name = "Bill"; Job = "Wizard"; Level = 5};;
val p1: Player = { Name = "Bill"
                   Job = "Wizard"
                   Level = 5 }

> p1.Job <- "Knight";;
val it: unit = ()

> p1.Level <- p1.Level + 1;;
val it: unit = ()

> p1;;
val it: Player = { Name = "Bill"
                   Job = "Knight"
                   Level = 6 }
  • 判別共用体(discriminated union, DU)
    • 識別子パターン
    • ケース識別子には値を付けられる。値はタプルでもいいので、つまり、いくつでも付けられる。
    • 変数の先頭は小文字、ケース識別子の先頭は大文字。それでどっちを意図しているか見分ける

リテラルパターンの例はこんな感じ

> [<Literal>]
- let Paranoia = "F#";;
[<Literal>]
val Paranoia: string = "F#"

> "F#" |> function Paranoia -> "STAY GOLD" | _ -> "STAY AWAY";;
val it: string = "STAY GOLD"
  • 構造的な型と比較
    • ここまで説明したのは全部構造的な型で、構造的な型の大小は、要素の定義順に比較して行われる
  • option型
  • ジェネリクス
    • 型パラメータの書き方は、OCamlスタイルと.NETスタイルがあるが、.NETスタイルがオススメ
  • 測定単位

単位を付けられるのは面白い。物理屋はよく式が正しいことを次元でチェックするけど、あれみたいなことですな。

  • ボクシング
    • box関数でボクシングすると、obj型になる
    • :?演算子で型チェックできる
    • :?パターン
  • 例外
    • failwithでSystem.Exception型の例外を投げられる。failwithfは引数にprintfフォーマットを使える
    • ユーザー定義の例外はexceptionで定義して、raiseで投げる
    • try...withでキャッチする。例外を投げ直すときはreraiseする。raiseするとスタックトレースが途切れる

これで5章は終わり