OCaml一人勉強会 - 演算子の使い方と定義

触れたくなくて今まで華麗にスルーしてきましたが、手続き型言語の多くとは違い、OCaml演算子は非常に融通が利きません。

# let x = 1 * 2.2;;
Characters 12-15:
  let x = 1 * 2.2;;
              ^^^
This expression has type float but is here used with type int

まあ勿論これは型推論のためなんですが…こういうときのために float_of_int, int_of_float という関数が用意されています。

# float_of_int(1);;
- : float = 1.
# int_of_float(1.1);;
- : int = 1

気を取り直して。

# let x = float_of_int(1) * 2.2;;
Characters 8-23:
  let x = float_of_int(1) * 2.2;;
          ^^^^^^^^^^^^^^^
This expression has type float but is here used with type int

エーッ。
これは演算子 * が int 型の引数を受け取る演算子だからです。
float 型の引数を受け取る演算子は *. になります。

# let x = float_of_int(1) *. 2.2;;
val x : float = 2.2

まあ勿論これも型推論のためなんですが…長い道のりでした。
若干話は変わりますが、単項演算子 - も中々癖物です。こんなコードを書くと…

# let succ x = x + 1;;
val succ : int -> int = <fun>
# succ -3;;
Characters 0-4:
  succ -3;;
  ^^^^
This expression has type int -> int but is here used with type int

(succ) (-) (3) と解釈されます。ちゃんと括弧を使いましょう。

# succ (-3);;
- : int = -2

ちなみにこの - ですが、 int にも float にも使えてしまいます。勿論 -. もあります。

# -1;;
- : int = -1
# -1.2;;
- : float = -1.2
# -.1.2;;
- : float = -1.2

勿論そんな特例があるわけではなく、リテラルとして使えるという話のようです。

float-literal ::= [-] { 0...9 }+ [. { 0...9 }] [(e| E) [+| -] { 0...9 }+]

なのでこういうのはダメ。

# let f = 1.0;;
val f : float = 1.
# -f;;
Characters 1-2:
  -f;;
   ^
This expression has type float but is here used with type int
# -.f;;
- : float = -1.

なんだかなー。


さて OCaml では括弧で括ることで演算子を定義することが出来ます。ちょっとイタズラして、整数の加算を減算にしてしまいましょう。

# let (+) x y = x - y;;
val ( + ) : int -> int -> int = <fun>
# 3 + 2;;
- : int = 1

こんなことする悪い子には罰があたります。具体的にどういう罰かというと、中々直せなくなります。

# let (+) x y = x + y;;
val ( + ) : int -> int -> int = <fun>
# 3 + 4;;
- : int = -1

アッー。
まあこんな感じで直しときましょう。

# let (+) x y = x + -y;;
val ( + ) : int -> int -> int = <fun>

寄り道しすぎた。
えーと OCaml では割と自由に演算子の定義が出来ます。和と差のリスト、積 と 商を返す演算子は下のように。

# let (+-) x y = [x + y; x - y];;
val ( +- ) : int -> int -> int list = <fun>
# let ( */) x y = [x * y; x / y];; 
val ( */ ) : int -> int -> int list = <fun>

(*/) と書くとコメントと解釈されるので注意。勘弁してくれという感じですが。
優先順位は一文字目の記号で決まります。

# let (+?) x y = x - y;;
val ( +? ) : int -> int -> int = <fun>
# let ( *?) x y = x / y;;
val ( *? ) : int -> int -> int = <fun>
# 4 +? 4 *? 2;;
- : int = 2

頭に ~ をつけることで単項演算子も定義できます。 - 以外定義できないみたいですが。
まあそんな感じ。