Pythonで型を意識するなら
近年、Pythonではプリミティブな型やtypingで型アノテーションを記述し、mypyで型チェックするという流れができているようです。例えばint
型を2つ受け取り、int
型を返すplus関数がある場合、以下のように型アノテーションを書きます。
>>> def plus(x: int, y: int) -> int:
... return x + y
...
しかし、これはあくまでアノテーションでしかなく、ドキュメントと同じようなもので、型を指定しているわけではありません。実際使ってみますと、
>>> plus(1, 2)
3
>>> plus("A", "B")
'AB'
>>> plus([1], [2])
[1, 2]
+
演算子が使える型ならば、問題なく動作します。したがって、以下のように、引数の型を精査するように関数を書き換えると、型を指定することができます。
>>> def plus2(x: int, y: int) -> int:
... if not (isinstance(x, int) and isinstance(y, int)):
... raise TypeError
... else:
... return x + y
...
>>> plus2(1, 2)
3
>>> plus2("A", "B")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in plus2
TypeError
>>> plus2([1], [2])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in plus2
TypeError
OCamlではどうなるでしょうか?
OCamlではdef
ではなく、let
を使います。
# let plus x y = x + y ;;
val plus : int -> int -> int = <fun>
+
演算子はint型でしか使えないので、引数の型はわざわざ書かなくてもOCamlの型推論機構によってint型であると確定します。したがって、int型以外を渡すとエラーになります。
上記の関数は以下のように書いても問題ありません。
# let plus (x : int) (y : int) : int = x + y ;;
val plus : int -> int -> int = <fun>
ちなみにOCamlでは、変数も関数もlet
で定義します。
# let x = 0 ;;
val x : int = 0
関数も実は以下のような変数と同じなのです。
# let plus = fun -> x (fun y -> x + y) ;;
val plus : int -> int -> int = <fun>
# plus 1 2 ;;
- : int = 3
# let plus_1 = plus 1 ;;
val plus_1 : int -> int -> <fun>
# plus_1 2 ;;
- : int = 3
Pythonで書くならば、以下のような形になるでしょう。
>>> plus = lambda x: lambda y: x + y
>>> plus(1)(2)
3
>>> plus_1 = plus(1)
>>> plus_1(2)
3
>>> plus_1(3)
4
浮動小数点数はどうなんだ?
Pythonを含め、多くの言語では整数も浮動小数点数も四則演算の演算子は同じですが、OCamlははっきりと分けられています。整数の四則演算は+ - * /
で、浮動小数点数の四則演算は+. -. *. /.
と、演算子にも小数点が付きます。では冪乗は**
と**.
なのか?
実はこれが違うのです。標準では冪乗は浮動小数点数のみを扱うようになっていて、**.
という演算子はありません。おかしい。
ということで、標準ライブラリの諸々のおかしいやら不便やらを解決しているのが、baseライブラリで、baseをより多機能に拡張したものがcoreライブラリとなっています。
余談ではありますが、皆さん「累乗」と「冪乗」の違いご存知でしたか?私は知りませんでした。「累乗」は、指数が自然数(つまり1以上の正の整数)の場合、「冪乗」は指数に自然数以外もいけるやつだそうです。
# 2 ** 3 ;;
Error: This expression has type int
but an expression was expected of type float
Hint: Did you mean `2.'?
# open Base ;;
# 2 ** 3 ;;
- : int = 8
# 2. **. 3. ;;
- : float = 8.
「Real World OCaml」では基本的にbaseやcoreを使って章が進んでいきます。
リスト操作の違い
Pythonではリスト内包表記というものがあります。
>>> l = [x for x in range(10)]
>>> l
[0,1,2,3,4,5,6,7,8,9]
OCamlではどうなるでしょうか?
残念ながらリスト内包表記はないようです。したがって、上記と同じことをするには自前で何とかします。range関数を作成したり、例えば、
# let l = List.init 10 (fun y -> y) ;;
val l : int list = [0;1;2;3;4;5;6;7;8;9]
baseを使うなら、
# open Base ;;
# let l = List.init 10 ~f:(fun y -> y + 1) ;;
val l : int list = [0;1;2;3;4;5;6;7;8;9]
というような方法もあります。標準ライブラリとbaseでのちょっとした違いに、関数のラベル付き引数があるように思います。上記のコードでは~f:...
のところです。私はラベル付き引数のほうがわかりやすくていいなと思っています。
まとめ
今回はPythonを比較対象としつつ、他言語と比較しつつOCamlを魅力を伝えていくシリーズを始めてみました。またRustやCommon Lispなどとの比較も面白いと思うので、今後うまくシリーズ化できるかはまだわかりませんが、ゆるゆると書いていこうと思います。