学習の記録−3

再帰はなるべく末尾再帰(引数が大きくなっても展開式の大きさが一定になるような再帰)を使って書く。

//普通の再帰
let rec fact n = 
    if n > 1 then n * fact (n - 1) else n;;
//末尾再帰
let fact_tailrec n = 
    let rec fact n res = 
        if n > 1 then fact (n-1) res * n else res
    in fact n 1;;

インライン(inliine)関数を作成すると、それはコンパイル時にインライン展開(コンパイル時に行われるβ変換)されて、関数定義による式の実行が行われる。β変換は通常、実行時に行われる。

> let inline add x y = x + y
^a ->  ^b ->  ^c
    when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^c)

> add 4 5;;
val it : int = 9

厳密な10進数扱いたかったらdecimal型を使うべし。

> 10M;;
val it : decimal = 10M

文字列中で\のエスケープ文字としての性質消して使いたかったら始めに@いれておく

let path1 = @"C:\Documents and Settings\Administrator\My Documents\Visual Studio 2010"

文字列操作は以下のような感じでできる。

> let str = "ABCDEFG";;

val str : string = "ABCDEFG"

> str.[1];;
val it : char = 'B'
> str.[*];;
val it : string = "ABCDEFG"
> str.[4..6];;
val it : string = "EFG"
> str.[..4];;
val it : string = "ABCDE"

名前空間やモジュールをインポートするにはopenを使って

open Checked;;

みたいに書く。

オーバーロード演算子のデフォルト解決

F#では適用可能な引数の中でデフォルトを1つ適当に決めているらしい。たとえば以下の足し算関数の型はなんでもいいはずなんだけどintになる。

> let add x y = x + y;;

val add : int -> int -> int

これを避けるためには型をちゃんと指定すること。

> let add (x : float) (y : float) = x + y;;

val add : float -> float -> float
パターンマッチ

以下のような感じで書く。返り値の型は揃えておくこと。

let f x = 
    match x with
    | 1 -> "one"
    | 2 -> "two"
    | _ -> "other"
> f 5;;
val it : string = "other"
> f 1;;
val it : string = "one"
let g x = 
    match x with
    | 1 | 2 | 3  as y -> "Less than 3 : " + string y
    | _ -> "Other"
> g 1;;
val it : string = "Less than 3 : 1"
> g 5;;
val it : string = "Other"
let mysign x = 
    match x with
    | _ when x > 0 -> 1
    | _ when x < 0 -> -1
    | _ -> 0
> mysign 10;;
val it : int = 1
> mysign -10;;
val it : int = -1
> mysign 0;;
val it : int = 0
> 

上のと等価

let mysign2 x = 
    match x with
    | 0 -> 0
    | _ -> (if x > 0 then 1 else -1)
> mysign2 10;;
val it : int = 1
> mysign2 -10;;
val it : int = -1
> mysign2 0;;
val it : int = 0
function式(fun+matchのような動作)
let mysign = function
    | 0 -> 0
    | x -> (if x > 0 then 1 else -1)
> mysign -10;;
val it : int = -1
> mysign 10;;
val it : int = 1
> mysign 0;;
val it : int = 0

気がついたんだけど、ワイルドカード使わないで引数そのまま書いても同じように動作するな、これ。

レコード(名前付きのタプル、Cでいう構造体的なもの)
> type Person = { Name : string; Age : int;};;

type Person =
  {Name: string;
   Age: int;}

> let taro = {Name = "Taro"; Age = 10};;

val taro : Person = {Name = "Taro";
                     Age = 10;}

「.」やパターンマッチで値取り出し可能。レコード名の前にちゃんとPerson.をつけないと警告が出て怒られる。

> taro.Name;;
val it : string = "Taro"
> taro.Age;;
val it : int = 10
> let {Person.Name = x; Person.Age = y} = taro;;

val y : int = 10
val x : string = "Taro"
> let name = function
    | {Person.Name = "Taro"; Person.Age = _} -> "This is Taro."
    | _ -> "Other";;

val name : Person -> string

> name taro;;
val it : string = "This is Taro."

元の変数の一部分だけを変えてのコピーも可能

> {taro with Age = 20};;
val it : Person = {Name = "Taro";
                   Age = 20;}

各フィールド(NameとかAge)はmutableにもできるらしいけど、それやるとF#の良さが減るのであまりやらないこと

判別共有体

enumがパワーアップしたようなもの。matchでうまく値をいじれるのが良い。それぞれの要素(以下だとMale、Female等)をケース識別子という。

>type Sex = Male | Female | Other of string;;

type Sex =
  | Male
  | Female
  | Other of string

> let x = Male;;

val x : Sex = Male

> let z = Other "Nekama";;

val z : Sex = Other "Nekama"
> let gender = function
    | Other s -> s
    | _ -> "Normal";;

val gender : Sex -> string

> gender x;;
val it : string = "Normal"
> gender z;;
val it : string = "Nekama"
option型(値があるかないかを示す)

SomeとNoneをケース識別子とする判別共有体。計算結果がない場合がある時に返却値として使うといい。

> Some "something";;
val it : string option = Some "something"
> None;;
val it : 'a option = None
ジェネリック(多相性)

単一のコードで複数の型に対する計算を可能にする技。C++でいうtemplateみたいなもん。Ocaml互換と.NET framework互換の書き方の2つがある。

まずは.NETの方から。<'T>は型名の後にスペースいれると怒られるので、くっつけて書かないといかん。

> type KeyValue<'T> = {Key : string; Value : 'T;};;

type KeyValue<'T> =
  {Key: string;
   Value: 'T;}

> {Key = "Taro";Value = 100};;
val it : KeyValue<int> = {Key = "Taro";
                          Value = 100;}
> {Key = "Taro";Value = 100M};;
val it : KeyValue<decimal> = {Key = "Taro";
                              Value = 100M;}
> {Key = "Taro";Value = "AAA"};;
val it : KeyValue<string> = {Key = "Taro";
                             Value = "AAA";}

次はocaml互換。'Tを書く位置がちょっとだけ違う。

> type 'T KeyValue = {Key : string; Value : 'T;};;

type 'T KeyValue =
  {Key: string;
   Value: 'T;}

> {Key = "Taro";Value = 100};;
val it : int KeyValue = {Key = "Taro";
                         Value = 100;}
> {Key = "Taro";Value = 100M};;
val it : decimal KeyValue = {Key = "Taro";
                             Value = 100M;}
> {Key = "Taro";Value = "AAA"};;
val it : string KeyValue = {Key = "Taro";
                            Value = "AAA";}
測定単位

円とかkmとかの単位を指定できる。

> [<Measure>] type km;;

[<Measure>]
type km

> [<Measure>] type hour;;

[<Measure>]
type hour

> 1<km> + 4<km>;;
val it : int<km> = 5
> 5.0<km>/2.0<hour>;;
val it : float<km/hour> = 2.5
> 5.0<km/hour> * 10.0<hour>;;
val it : float<km> = 50.0
> 3<km>*5<km>;;
val it : int<km ^ 2> = 15

レコードに単位をつけることもできる。

> type vector2D<[<Measure>] 'u> = {X : float<'u>; Y : float<'u>};;

type vector2D<'u> =
  {X: float<'u>;
   Y: float<'u>;}

> {X = 1.0<km>; Y = 3.5<km>};;
val it : vector2D<km> = {X = 1.0;
                         Y = 3.5;}
> {X = 1.0<km/hour>; Y = 3.5<km/hour>};;
val it : vector2D<km/hour> = {X = 1.0;
                              Y = 3.5;}