学習の記録−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"
オーバーロード演算子のデフォルト解決
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;}