ディレクトリについてメモ

チルダ展開

ユーザーのホームディレクトリを省略できる。
ユーザー名「green_csharper」としてそのホームディレクトリ以下のdocディレクトリに移動する場合。

$ cd ~green_csharper/doc

けど、自分が触った事ある環境だとユーザーのディレクトリは「/home」直下にユーザー名でディレクトリが用意されてるのでそれほどありがたみは無いかも。

ちなみに「~」はログインユーザー自身のホームディレクトリはさらに省略できます。
つまり上記と同じ例だとこうなる。

$ cd ~/doc

これだとだいぶ便利になりますね。

作業ディレクトリを表示する「pwd

そのままですね、カレントディレクトリの完全パスを教えてくれます。

$ cd ~/opt
$ pwd
/home/green_csharper/opt

一つ前の移動元へ戻る「cd -」

これ知りませんでしたorz

$ cd /etc
$ pwd
/etc
$ cd ~/opt
$ pwd
/home/green_csharper/opt
$ cd -
$ pwd
/etc

すごく便利です!

必ずいる隠しファイル「.」「..」

ディレクトリ内を一覧する「ls」コマンドにオプションで「a」を指定して隠しファイルも表示させると、必ず「.」と「..」がいますよね。なんとなくスルーしてたんですが、彼らも隠しファイルだそうです。
ご存知の通り

を指しています。
ルートの場合は自分自身が親になるそうですよ。

ワイルドカード展開(またはグロビング - globbing)

以下が最も基本的な4種類だそうです。

ワイルドカード 一致対象
? 任意の1文字
* 任意も文字列
[set] setに含まれている文字
[!set] setに含まれていない文字
使い方

「?」と「*」に関してはそのままなので割愛します。
ちなみ「*」ですが、C#とかでファイルの一覧取得する時はよく「*.*」としますが、Unix系では「*」のみで指定します。
というか、「*.*」とかやっちゃうと、「.」が入ってるファイルしか取ってこないからダメになってしまうのです。
Unix系では「.」に特別に意味はなく、文字の一つに過ぎないからということらしいですね。

「[set]」は指定した1文字という事です。
例えば

  • [abcde] ならば a, b, c, d, eのどれか
  • [a-e] ならば 上記と同じ
  • [a-z] ならば 小文字のアルファベットのどれか
  • [0-9] ならば 0から9までの数字のどれか
  • [a-zA-Z] ならば 小文字・大文字のアルファベットのどれか

となります。
最初に「!」を入れると否定になります。

記号も含められます。ただし、ハイフンは範囲指定に使っているので最初か最後にしか指定できません。

  • [-.;,_]

また、「!」自体を照合するにはセットの1文字目の後に指定するか、「[\!]」のようにバックスラッシュを前に付けて指定します。

ワイルドカードの組み合わせ

ワイルドカード展開は組み合わせて使えます。

  • *.[ab] (ピリオドに続いてa, bで終わる全てのファイル -> sample.a, test.b)

とっても便利ですね。
ちなみにこれらはパス名展開と呼ばれる概念の一部だそうです。
lsコマンドなどと組み合わせると力を発揮します。

$ ls /usr[0-9]/*.sh <- /usrの後に数字1文字が続く全てのディレクトリの中の.shファイル全てを一覧する(/usr1/test.sh)

ブレース展開

所定の書式に則って任意の文字列を展開します。

  • オプションの序文、それに続く中括弧で囲まれたカンマ区切りの文字列、それに続くオプションの追文で構成されます。
使い方

「..」での特殊な展開や、入れ子もできます。

$ echo {a,b,c}
a b c
$ echo pre{a,b,c}
prea preb prec
$ echo {a,b,c}post
apost bpost cpost
$ echo {a..e}
a b c d e
$ echo {1..5}
1 2 3 4 5
$ echo a{b{c,d},e}f
abcf abdf aef

ちなみにカンマで並べる文字列の間に気を効かして空白を入れて「{a, b, c}」とかするとダメでした。

ワイルドカード展開と併用する事もできます。
以下の2つは同じ結果を返します。

$ ls *.[cho]
$ ls *.{c,h,o}

パターンマッチ

入力、つまり関数のパラメータが組、レコード、リストのような構造データになるとそのデータの各要素にアクセスする必要があります。

C#なら、プロパティ、メソッド、インデクサとか、リストならイテレートして走査していくわけですが、OCamlは違うみたいです。
(レコードに関してはプロパティっぽくアクセスもできるけど..)

そこで必要になってくるのがパターンマッチということです。

構文

match  with
  パターン -> 

おお、矢印ですね、C#ラムダ式っぽくて好きです。

組へのパターンマッチ

# let f point = match point with
   (x, y) -> "point x:" ^ string_of_int x ^ " y:" ^ string_of_int y;;
val f : int * int -> string = <fun>
# f (5, 10);;
- : string = "point x:5 y:10"

う〜ん、なんかすごく下らないサンプルですね。
2点間の距離とか計算させようとしたんですが、眠いのであきらめます。。

組・レコード・リスト

無理やり言えばC#のKeyValuePairみたいなものかな。任意の2つのデータを一つとして扱えます。

構文
  • (要素, 要素)
# (2.5, 3);;
- : float * int = (2.5, 3)
# (2.5, false);;
- : float * bool = (2.5, false)

レコード

よく考えればC#にはこんなデータ構造は無いかもしれない。(DataRowとか使えば別だが...)
複数の名前と値のペアを一つのレコードとして扱える。

構文
  • { 名前1 = 値1; 名前2 = 値2; ..... }

名前の事をフィールドというそうです。

まずレコードの型を定義します。

# type staff_t = {
    id : int;       (* ID *)
    name : string;  (* 名前 *)
    unit : string;  (* 部署 *)
    age : int;      (* 年齢 *
  };;
type staff_t = { id : int; name : string; unit : string; age : int; }

使います。省略すると怒られます。

# { id = 101; };;
Characters 0-13:
  { id = 101; };;
  ^^^^^^^^^^^^^
Error: Some record field labels are undefined: name unit age
# { id = 101; name = "test staff"; unit = "development"; age = 27 };;
- : staff_t = {id = 101; name = "test staff"; unit = "development"; age = 27}

リスト

任意の個数をもち、順番に並んだデータです。

定義
  • [] は空リスト
  • 『::』は『リストの先頭に要素を付け加える』という命令
  • リストの要素は全て同一の型である必要がある

上記を踏まえて、2種類の定義方法があります。

  • 要素1 :: 要素2 :: ... :: 要素n :: []
  • [要素1; 要素2; ... ; 要素n ]
# "one" :: "two" :: "three" :: "four" :: "five" :: [];;
- : string list = ["one"; "two"; "three"; "four"; "five"]

『::』の命令方法を考えると、ちゃんと書くとこう。

# "one" :: ("two" :: ("three" :: ("four" :: ("five" :: []))));;
- : string list = ["one"; "two"; "three"; "four"; "five"]

つまり『::』は『リスト以外の左辺 :: リストの右辺』にのみ有効ということ。
ということは、リストは自身であるリストを参照しなければ作る事ができないということ。
その為に空リストである『』が用意されている。
よって、『::』を使って作成するリストの最後の右辺は常に『
』なのである。(たぶん..)

それはさておき、もう一つの例

# ["one"; "two"; "three"; "four"; "five"];;
- : string list = ["one"; "two"; "three"; "four"; "five"]

結果を考えると、こっちのが書き方としては楽だし直感的な気がする。

条件分岐

if 条件 then 式 else 式
  • 「条件」はboolかboolを返す式
  • 2つの「式」は同じ型を返す

条件文というか条件式?C#みたく分岐してごにょごにょって感じではないな。
どっちかというと三項演算子みたいな雰囲気。

(* 絶対値を返す *)
# let abs_value x = 
    if x > 0.0 then x
               else -. x;;
val abs_value : float -> float = <fun>
# abs_value (-5.);;
- : float = 5.

ということは、C#では

if (x < 0) {
  // x < 0の場合
} else if (x < 10) {
  // 0 <= x < 10 の場合
} else if (x < 20) {
  // 10 <= x < 20 の場合
} else if (x < 30) {
  // 20 <= x < 30の場合
} else {
  // その他
}

だと、OCamlでは

if x < 0 then "x < 0の場合"
         else if x < 10 then "0 <= x < 10 の場合"
                        else if x < 20 then "10 <= x < 20 の場合"
                                       else if x < 30 then "20 <= x < 30の場合"
                                                      else "その他"

となるわけか。

変数と関数

変数の定義

  • let 変数名 = 式
  • 先頭の文字はアルファベットの小文字だけ
  • 他の言語でいうところの定数に近い。つまり書き換える事ができません。(方法はあるみたい)
# let salestax = 1.05;;
val salestax : float = 1.05
# 1000. *. salestax;;
- : float = 1050.
# let hello = "Hello";;
val hello : string = "Hello"
# hello ^ " World !!";;
- : string = "Hello World !!"

関数の定義

  • let 関数名 引数 ... = 式
  • 先頭の文字はアルファベットの小文字だけ
  • 関数の引数・返り値の型は型推論により決定される
  • 実行時に引数の型チェックが行われる
関数の使用
  • 関数名 引数 ...
# let salestaxcalc x = x *. 1.05;; 
val salestaxcalc : float -> float = <fun>
# salestaxcalc 1000.;;
- : float = 1050.
# let salestaxDiscountCalc discount price = (price -. discount) *. 1.05;;
val salestaxDiscountCalc : float -> float -> float = <fun>
# salestaxDiscountCalc 100. 1000.;;
- : float = 945.

ファイルからの関数読み込み

まずファイル
C:\ocaml\salestaxDiscountCalc.ml

(* 割引した後消費税を計算する *)
let salestaxDiscountCalc discount price = (price -. discount) *. 1.05
読み込んで使う
# #use "c:\\ocaml\\salestaxDiscountCalc.ml";;
val salestaxDiscountCalc : float -> float -> float = <fun>
# salestaxDiscountCalc 100. 1000.;;
- : float = 945.

データ型 一部だけ

整数

最大で1073741823だそうです。というか32bitCPUでは31ビット符号付整数という事みたいです。
64bitCPUだと63ビット符号付整数みたいです。

インタプリタで実行

# 1;;
- : int = 1
# 1073741823;;
- : int = 1073741823
# 1073741824;;
- : int = -1073741824 <= コレは何??
# 1073741825;;
Characters 0-10:
  1073741825;;
  ^^^^^^^^^^
Error: Integer literal exceeds the range of representable integers of type int
# -1;;
- : int = -1
# -1073741823;;
- : int = -1073741823
# -1073741824;;
- : int = -1073741824
# -1073741825;;
Characters 1-11:
  -1073741825;;
   ^^^^^^^^^^
Error: Integer literal exceeds the range of representable integers of type int
整数で演算

演算子は以下の4つ

  • 加 +
  • 減 -
  • 乗 *
  • 商 /
  • 余り mod

数学と同じ計算。乗除が加減より優先、括弧で括ると括弧内優先。

インタプリタで実行

# 100 + 5;;
- : int = 105
# 100 - 5;;
- : int = 95
# 100 - 105;;
- : int = -5
# 100 * 5;;
- : int = 500
# 100 / 5;;
- : int = 20
# 100 / 6;;
- : int = 16
# 100 mod 5;;
- : int = 0
# 100 mod 6;;
- : int = 4
# (5 + 8) * 3;;
- : int = 39
# 5 + 8 * 3;;
- : int = 29
# 5 / 2 * 4;;
- : int = 8
# 5 * 4 / 2;;
- : int = 10

実数

OCamlでは整数と実数は厳格に区別されるそう。演算子が整数用のものは使用できません。
また、余りが無い代わりに、実数にはべき乗の演算子と無限大を表す値があります。

  • 加 +.
  • 減 -.
  • 乗 *.
  • 商 /.
  • べき乗 **
  • 無限大 infinity
  • マイナス無限大 neg_infinity

無限大は実数と同じように扱えます。

# infinity;;
- : float = infinity
# 5.5 +. infinity;;
- : float = infinity

また、実数の演算では、小数点以下が0の場合でも小数点は書く必要があります。

# 1.5 + 1.0;;
Characters 0-3:
  1.5 + 1.0;;
  ^^^
Error: This expression has type float but is here used with type int
# 1.5 +. 1;;
Characters 7-8:
  1.5 +. 1;;
         ^
Error: This expression has type int but is here used with type float
# 1.5 +. 1.
  ;;
- : float = 2.5
# 1.5 +. 1.0;;
- : float = 2.5

文字列

引用符"…"で囲みます。

# "Hello OCaml";;
- : string = "Hello OCaml"

文字列を連結するには『^』を使います。

# "Hello " ^ "OCaml";;
- : string = "Hello OCaml"

真偽値

true, falseで型はbool

論理演算
  • かつ &&
  • または ||
  • 否定 not

論理演算の優先順は

  1. 『否定』
  2. 『かつ』
  3. 『または』
比較演算子
  • 等しい =
  • 等しくない <>
  • より大きい >
  • より小さい
  • 以上 >=
  • 以下 <=

とか。SQLみたいですね。

比較演算子は整数、実数のどちらにも使用できるようです。
ただし、比較する型は同じである必要があります。

# 10 < 100;;
- : bool = true
# 10.5 < 100;;
Characters 7-10:
  10.5 < 100;;
         ^^^
Error: This expression has type int but is here used with type float
# 10.5 < 100.;;
- : bool = true

とりあえずここまで。