学習の記録−10

今度は

の本の内容を適当にメモ。

open System
(*
複数行コメント
*)
[<EntryPoint>]
let main(args : string[]) = 
    ///斜線3つで書いたコメントはIDEの補完で表示される
    let greeting, thing = args.[0], args.[1]
    let timeOfDay = DateTime.Now.ToString("hh:mm tt")
    printfn "時刻 %s : %s, %s" timeOfDay greeting thing
    0

これをF# Interactiveに送って実行すると

> main[|"Hello"; "World"|];;
時刻 11:25 午前 : Hello, World
val it : int = 0

他の言語と違ってソースファイルをコンパイルする順番が大事。既にコンパイル済みor同一ファイル内で定義済みのものしかつかえないのでファイルの順序をただしく依存関係の低い順に並べておくこと。

ignore関数は関数の返り値を無視してunitを返してくれる。

> let f x = x*x;;

val f : int -> int

> f 4;;
val it : int = 16
> ignore(f 4);;
val it : unit = ()

複雑なリスト内包表記も書けるのか。

let x = 
    [
        let negate z = -z
        for i in 1..10 do
            if i % 2 = 0 then
                yield (negate i)
            else
                yield i];;
>val x : int list = [1; -2; 3; -4; 5; -6; 7; -8; 9; -10]

名前空間とモジュールの違い

  • 名前空間はネスト不可・同一ファイルに複数定義できるが、型の”宣言”のみで値の定義はできない。
  • モジュールはネスト可能・同一ファイルに一個(ファイルの先頭)、値の定義も可能。

どっちかっつーと名前空間は大きめのOOP開発に使うもんで、モジュールは書き殴り系のもんにつかうらしい。

[]属性について

F#ではデフォルトとしてVisual Studioの最後に追加されているファイルが自動的にメインファイルとして実行されるんで、それが嫌な時には[]属性を使って明示的にメイン関数を定義する。メイン関数は

  • string型の引数を1つとる
  • intを返す
  • 最後にコンパイルされるファイル内に定義された最後の関数であること

の条件を満たす必要がある。

記号演算

C++でいう所の演算子オーバーロード的なこともできる(実際はただの関数だそうで)。例として階乗の計算を定義。

let rec (!) x = 
    if x <= 1 then 1
    else x * !(x-1);;
> !5;;
val it : int = 120

引数を2つ以上取る場合はデフォルトで一番目の引数が記号演算の左辺になる。

let (====) x y = 
    if x = y then
        Some(true)
    else
        None;;
> 1 ==== 1;;
val it : bool option = Some true
> 1 ==== 2;;
val it : bool option = None

関数合成

順次合成演算というようで”>>”を使う。手前にあるもの(左側にあるもの)から順に作用されるようになる。

> let f = (fun x -> 2*x) >> (fun x -> x+1);;
val f : (int -> int)
> f 10;;
val it : int = 21

その逆版の"<<"もあり。

名前付きパターンと[]属性

マッチさせるときに定数じゃなく可変な名前付変数にする場合の書き方。

let hello name = 
    match name with
    | "Taro" -> "Hello, Taro!"
    | x      -> sprintf "Hello, %s. How are you?" x;;
> hello "Taro";;
val it : string = "Hello, Taro!"
> hello "teramonagi";;
val it : string = "Hello, teramonagi. How are you?"

もし、定数をこの関数の外で指定したい場合には[]属性を使う。

[<Literal>]
let Bob = "Bob san";
let hello_mod name = 
    match name with
    | "Taro" -> "Hello, Taro!"
    | Bob    -> "Hi, Bob!"
    | x      -> sprintf "Hello, %s. How are you?" x;;
> hello_mod "Taro";;
val it : string = "Hello, Taro!"
> hello_mod "Bob san";;
val it : string = "Hi, Bob!"
> hello_mod "teramonagi";;
val it : string = "Hello, teramonagi. How are you?"

whenガードとパターンのグループ化

マッチの際に条件付けたりorっぽくしたりとこんなんもできますよと。

let func z = 
    match z with
    | 1 | 2 | 3 -> 100
    | x when x < 10 -> 10
    | _ -> 1 ;;
> func 2;;
val it : int = 100
> func 9;;
val it : int = 10
> func 2303;;
val it : int = 1

パターンマッチ使ってリストの長さを計算する

let rec listLength x = 
    match x with
    | []  -> 0
    | [_] -> 1
    | [_; _] -> 2
    | hd::tail -> 1 + listLength tail;;
> listLength [];;
val it : int = 0
> listLength [1];;
val it : int = 1
> listLength [1;2;3];;
val it : int = 3
> listLength [1..5];;
val it : int = 5

判別共有対とレコード

判別共有体とレコードは前者がenumっぽくて、後者が構造体っぽいもの。どちらもtypeを使って宣言する。

レコードのパターンマッチ

こんなかんじで。実行するとTypeがワゴンのもののみ取得される。

type Car = {Type : string; Age : int};;
let cars = [{Type="Wagon"; Age=0};{Type="Lanver"; Age=10}];;
cars |> List.filter (function |{Type = "Wagon"} -> true | _ -> false);;
val it : Car list = [{Type = "Wagon";
                      Age = 0;}]

遅延評価

lazy()で評価対象を括っておけ。一回評価されるとキャッシュされた値が返る。

> let y = lazy( printfn "yを評価中・・・"; 30);;
val y : Lazy<int> = 値は作成されていません。
> y.Value;;
yを評価中・・・
val it : int = 30
> y.Value;;
val it : int = 30