F#はマルチパラダイム言語ではありますが、コア互換のあるOCamlと同じく関数型ベースの言語です。
関数型言語は基本的に副作用を嫌います。 副作用を全く持たないものを純粋関数型とよび、副作用をもつことができるものを非純粋関数型言語といいます。F#は非純粋関数型言語です。
副作用とは、状態変化があり、以降の処理の結果を変えてしまうことをいいます。副作用の例として、変数の代入があり同じ変数を使った処理でも値(状態)が変わることで処理の結果は変わってしまう。
ちなみに、副作用を持たず同じ条件下では同じ結果が得られ、他の処理に影響を与えない性質を参照透過性という。関数型プログラミングでは参照透過性を保つことが良いプログラムとされる
値や関数の宣言するには、letを使用します。
書式 let x = expr
exprは式(値や関数もexpr)です。 変数 x に、exprを束縛するといいます。 具体的な例を見ていきましょう
> let one = 1;; val one : int
その後、1を束縛したoneは、1と評価される。
> one;; val it : int = 1
C言語などでは、コンパイラに型を指定する必要があるが、
F# は静的に型推論する。
> let one = "one" val it : string
型
F# では.NET の型をしようするが、それぞれ省略型が用意されている
ここでは主なものを紹介する
type int (int32) | System.Int32 | 32bit整数型 |
type bool | System.Boolean | ブール型(真偽) |
type string | System.String | Unicode文字列 |
type byte | System.Byte | 8bit符号無し整数 |
type float32 | System.Single | 32bit 浮動小数 |
type float | System.Double | 64bit 浮動小数 |
type decimal | System.Decimal | 非常に大きな整数(財務計算などで使う |
type exn | System.Exception | Exception(例外)型 |
type obj | System.Object, .NET | Frameworkでは全てのクラスがObjectを継承する |
数値では、u を付けることで符号無しとなる。
例
type uint32 など
値がないことを表します。 (C言語などのvoid型にあたります)
例
> ();; val it : unit = ()
> printf "hello world\n";; hello world val it : unit = ()
論理的にまとまった処理(手続)は、関数としてまとめることができます。関数型言語では、入力に対して出力が(基本的には※1)あることが基本です。 数学の関数に近い関数といえるでしょう ※1 例外的に、unit(値がない)を返すことができます 例
関数
> let sum x y = x + y;;
val sum : int -> int -> int
引数を与えて、関数を評価してみる
> sum 1 2;;
val it : int = 3
関数は下記のようにも書くことができる。
> fun x y -> x + y;;
束縛すれば、後で再利用できる。
> let sum = fun x y -> x + y;;
条件分岐
書式
if 条件式 then 条件式が真の時に実行する式 else 条件式が僞の時に実行する式
> let compare x y = if x > y then x else y;;
val compare : 'a -> 'a -> 'a
else は省略できる
if 1 > 0 then printf "1は0より大きい";;
パターンマッチ
match文
パターンマッチによる条件分岐です。
例として、数値を単語(文字列)に変換を定義します。
今回はソースファイルを作成し、コンパイルして実行してみます。
MatchSample.fs
let NumToString n = match n with | 1 -> "one" | 2 -> "two" | _ -> "another number" printf "%s\n" (NumToString 2);; printf "%s\n" (NumToString 3);;
実行結果
two another number
NumToString の引数に2を与えると、パターン 2 にマッチし、"two"を出力しています
NumToString の引数に3を与えると、パターン _ にマッチし、"another number"を出力します
※ _ はその他全てにマッチする
ちなみに、match は下記のように省略して書けます
function | 1 -> "one" | 2 -> "two" | _ -> "another number"
工事中
asパターンを使うと、パターンマッチした値全体を参照できます。
下記はConsオペレータでリストの先頭と残りを分解したパターンマッチですが、 asパターンを使うことによってリスト全体も参照しています(as listの部分がそれです)。
#light let AsPattern = function [] -> printfn "no match" | head::tail as list -> // リストの先頭 printfn "head=%d" head // 先頭をとった残りのリスト printf "tail=[" ignore (List.map (printf " %d") tail) printfn " ]" // リスト全体 ( as listにより参照 printf "list=[" ignore (List.map (printf " %d") list) printfn " ]" AsPattern [1;2;3]
実行結果
head=1 tail=[ 2 3 ] list=[ 1 2 3 ]
(**)
例
(* 複数行コメント この部分はコンパイル対象外となります。 *)
//
例
// 一行コメント
リストは、同じ型の値を並べたものです。
リストは多相型なので、整数、文字、タプルなど、さまざまな要素を持つことができます。
要素1, 2, 3をもつリストを定義してみます。
> let list = [1;2;3];;
評価結果
val list : int list
リストは、Consオペレーターを使って下記のようにも書けます
let list = 1::2::3;;
Consオペレーターは、リストの先頭に要素を加えるための演算子です。
パターンマッチで、リストを先頭要素と残りの要素に分離するときも使います。
2つのリストを1つのリストに結合します。
> ["one"] @ ["two"];;
val it : string list = ["one"; "two"]
工事中
レコードとは、論理的に関連のあるデータをまとめたものを
新たなデータ型(レコード型)として定義したものです。
C言語をご存知の型なら、構造体にあたる概念です
下記に使用例を示します
// 生徒を表すレコードを定義します。 type student = { name: string; grade: int; };; // student型の値を作成してみます。 let highschool_student = { name = "taro"; grade = 11; };; // レコードのそれぞれのフィールドにアクセスしてみます。 printf "name=%s,grade=%d\n" highschool_student.name highschool_student.grade;;
バリアントは、論理的に関係のある値を統一して扱うのに便利な機能です。
※OCamlではバリアントという名称ですが、F#では Discriminated Unionとして紹介されているので、 今後そちらに用語を統一するかもしれません。
さっそく、サンプルコードといきたいと思います
私が好きなプログラミング言語を列挙し、FavoriteProgramLanguage型として定義します そして、パターンマッチによりそれらの値に対応する文字列を取得します。
#light type FavoriteProgramLanguage = FSharp | CSharp | CPlusPlus | Perl | Java let GetString = function FSharp -> "FSharp" | CSharp -> "CSharp" | CPlusPlus -> "C++" | Perl -> "Perl" printfn "%s" (GetString FSharp)
ここで、Java がパターンに含まれていないことにお気づきでしょうか?
実はこのプログラム、コンパイルするとWarningが発生します。
なぜでしょうか。 バリアント型として定義された型をパターンマッチする場合、
すべての値がパターンマッチによって調べられていないとコンパイラが警告してくれるからです。
このことにより、値を追加した時にの実装忘れによるバグが未然に防がれます。 強力な静的型付け言語であるF#の本領発揮というわけです。
int a = 5 // aには5という値がある。 int b = NULL; // bは値を持っていない。
Someのサンプルです
> let value = Some(5);; val value : int option
> let noValue = None;; val noValue : 'a option
> let checkValue val = match val with | Some(_) ->true | None -> false;; val checkValue : 'a option -> bool
> checkValue value;; val it : bool = true > checkValue noValue;; val it : bool = false
カリー化とは引数を2つ以上とる関数について、下記のような関数を作る技法です。
・引数
関数のひとつ目の引数
・戻り値
もとの関数の残りの引数をとり結果を返す関数
カリー化の例を示します。
> let add a b = a + b;; val add : int -> int -> int
次に、先ほどのadd 関数をカリー化した例を示します。
> let add a = fun b -> a + b;; val add : int -> int -> int
ひとつめの引数 a を受け取って、残りの引数 b をとり、結果(a+b)を返す関数として定義しました。
工事中
関数を引数に取ったり、関数を結果として返す関数を高階関数といいます。
関数型言語では高階関数は非常によく使われます。
代表的な高階関数を紹介します。
fold関数はリストの要素を順番に関数で評価していき、結果を蓄積していきます。
1から10までの和を求めます。
> List.fold (fun acc x -> acc + x) 0 [1..10];; val it : int = 55
下記のように書き直すこともできます。
> List.fold (+) 0 [1..10];; val it : int = 55
5の階乗を求めます。
> List.fold (fun acc x -> acc * x) 1 [1..5];; val it : int = 120
先ほどのサンプルと同じように下記のように書き直すこともできます。
> List.fold (*) 1 [1..5];; val it : int = 120