1 + 1 -- Int plus 1 + "1" -- Type error: "String is not an instance of Num" "1" + [] -- Type error
数と遊ぼう
多くのプログラミング言語、特に「実用的」な言語には、 xkcd でネタにされているように、数の扱いについておかしなコーナーケースがあったり一貫性が欠けていたりします。
このことは、数値用の演算子を数値でないものまで拡大解釈 (+
を文字列の連結とみなすような) して使用したり、数値と文字列との間で自動変換を行ったりすることにつながります。Frege では (Haskell もそうですが) 型が混在していると演算子が使えないため、このような問題は起こりえません。
しかしながら、実際のコンパイル時には、ある種の高度な推論が行われてプログラマに楽をさせてくれます。もともと型システムは、次のような式を許可しないはずです。
1 + 1.0
というのも、1
は Int
型で 1.0
は Double
型ですが、+
は演算されるふたつの数が同じ型であることを要求するからです。
コンパイラはある程度賢いため、このような単純な数値リテラルの場合に 1.0 + 1.0
と書くことを強制することはありません。1
が登場する式の型に関する制約を解くことで、プログラマが何を意図したのかを推測してくれます。もう一つ、少しだけ発展的な例を見てみましょう。
1 / 2
さっきと同じように 1
および 2
は Int
型の数値リテラルですが、Frege では除算の演算子は Int
型に対して (それ以外の Integral 型に対しても) 定義されてはいません。除算演算子は両方の被演算数が Real
型クラスに属する数値型であることを要求し、この場合 Frege では (Haskell と異なりデフォルトでは必ず) 具体型として Double
が採用されます。つまり、次のようになります。
1 / 2 == 0.5 -- type is Double
Note
|
Java では 1 / 2 は 0 に評価されるため、挙動がまったく違います。
|
このことは面白い帰結をもたらします。というのも、Java の Double
型は数多くの特殊ケースをうまく処理しているからです。
Double のいいところ
もっともよくある特殊ケースはゼロによる除算です。Frege ではどのように扱われているのでしょうか?
1 / 0 -- Infinity
Warning
|
数値の文字列表現は必ずしも数字のみからなるわけではないことに注意しましょう。小数点、マイナス記号や E の文字などは気付きやすいですが、Infinity や NaN (これは数値ではない) のことを忘れがちです。
|
無限大を含む式を計算するとどうなるでしょう? 何かに無限大を足した結果はやはり無限大です。無限大どうしを足すこともできます。しかし除算は定義されません。
(1/0) + 1 -- Infinity (1/0) * 3 -- Infinity (1/0) + (1/0) -- Infinity (1/0) * (1/0) -- Infinity (1/0) / (1/0) -- NaN
NaN
とは別に Infinity
が存在する利点は何でしょう? Infinity
が式の中に存在しても、通常の演算が可能な数値に戻せる場合があります。
1 / (1/0) -- 0.0
うん、なかなか面白いですね。その他、数学的に微妙な例について見てみましょう。
0 / 0 -- NaN (1/0) / 0 -- Infinity 0 ^ 0 -- 1
これは一見、数学における決まり事に反しているように見えます。
数学からの逸脱?
数学では、ゼロによる除算は定義されません。無限大による除算やゼロのゼロ乗も同様に未定義です。
しかしながら、コンピュータが計算しているのは近似値であり、実用上 の数値計算において最善を尽くしているにすぎません。Math.PI
や Math.E
を扱う際も精度には限界があります。所詮、平方根も単なる近似値に過ぎません。
import frege.prelude.Math Math.sqrt 2 * Math.sqrt 2 -- 2.0000000000000004
いずれにしても我々が扱える計算精度は有限であるため、ゼロ除算のような操作が行われた時、プログラムごと吹き飛んでしまわないよう、計算限界の範囲内でそれなりにうまく振る舞うように仕込みを入れておくのは悪いことではありません。
参考文献
XKCD | |
Numberphile on zero |