Haskell にはさまざまな数値型があります。これらは Scheme [7] の数値型をもとにしており、 Scheme は Common Lisp [8] をもとにしていま す。(しかし、これらの言語は動的な型付けをおこないます。) 標準の型には、 固定長および任意倍長の整数、それぞれの整数で構成された比(分数)、単精度お よび倍精度の実数、複素数の浮動小数が含まれています。ここでは数値クラス構 造の基本的性格の概略を説明します。詳細についてはレポートの §6.3 節を参照してください。
数値型クラス(クラス Num とその下にあるクラス)は Haskell の標準 クラスの多くを考慮しています。Num は Eq のサブクラスで すが、Ord のサブクラスではないこを注意しておきます。これは、順 序述語は複素数には適用できないことによります。しかしながら、 Num クラスのサブクラス Real は Ord のサブクラ スです。
Num クラスはすべての数値型に共通するいくつかの基本演算を備えて
います。これには、加算、減算、符号反転、乗算、絶対値が含まれています。
(+), (-), (*) :: (Num a) => a -> a -> a
negate, abs :: (Num a) => a -> a
[negate は Haskell では唯一採用された前置演算子 - (マイナス)
を適用する関数です。これの呼出しを (-) と書くことはできません。
それは、(-) と書けば、減算関数になってしまうからです。そこで、
この nagate という名前をかわりに使うわけです。
たとえば、-x*y は negate (x*y) と同値です。
(前置のマイナスは中置のマイナスとおなじ優先度をもちますので、当然、
乗算より優先度は低いわけです。)]
Num には除算演算子がないことに注意してください。 2 つの別の種類の除算演算子がオーバラップしない 2 つの Num のサ ブクラスで定義されています。クラス Integral では全数 整数除算演算と剰余演算子が提供されています。Integral の標準インスタンスは Integer (範囲制限のない整数あるいは数学で いうところの整数、一般には「bignum」としてしられているもの) と Int (範囲限定のある、計算機上の整数、範囲は最低でも 29 bit の符 号付 2 進数)です。一部の Haskell ではこれらのほかに別の integral 型が提 供されています。Integral は Num の直接のサブクラスでは なく、Real のサブクラスであることに注目してください。その意味す るところは、ガウス整数を提供するつもりはないということです。
他のすべての数値型はどれも Fractional クラスに属します。このク ラスは普通の除算演算子 (/) を備えています。そのサブクラスには、 Floating があり、三角関数、対数関数、指数関数を備えています。
Fractionnal と Real のサブクラスである
RealFrac は関数 properFraction を備えています。この関
数は数を整数部分と小数部分にわけます。また、それぞれ別のルールで整数に丸
める関数群もそなえています。
properFraction :: (Fractional a, Integral b) => a -> (b,a)
truncate, round,
floor, ceiling: :: (Fractional a, Integral b) => a -> b
RealFloat は Floating と RealFrac のサブクラ スで浮動小数部への効率てきなアクセスのためにいくつかの特別な関数を備えて います。それは、exponent と significand です。標準の型 Float と Double は RealFloat クラスになります。
標準の型、Int、Integer、Float、 Double はプリミティブです。その他はこれらからタイプ構築子により 構築されます。
Complex ( Complex ライブラリにあります) はタイプ構築子
であり、RealFloat 型から Floating クラスの複素数のクラ
スを構築します。
data (RealFloat a) => Complex a = !a :+ !a deriving (Eq, Text)
! の記号は正格性フラグです。これらについては 6.3 節で議論しました。
引数の型を限定している文脈 (RealFloat a) => に注目
してください。つまり、標準の複素数型は Complex Float であ
り、Complex Double だということです。また、data
宣言から、複素数は x :+ y のように書くということ
がわかります。引数はそれぞれ、実数部と虚数部です。:+ はデータ構
築子ですから、これをパターンマッチングに使用することができます。
conjugate :: (RealFloat a) => Complex a -> Complex a
conjugate (x:+y) = x :+ (-y)
同様にして、型構築子 Ratio (これは、Rational ライ
ブラリに含まれています)は、Integral のインスタンスから
RealFrac クラスの分数クラスを生成します。( Rational は
Ratio Integer の型シノニムです。) しかし、Ratio
は抽象型構築子です。データ構築子 :+ のかわりに、分数はふたつの
整数からひとつの分数を形成する「%」関数をつかいます。パターンマッ
チングのかわりに、構成要素を引出す関数が用意されています。
(%) :: (Integral a) => a -> a -> Ratio a
numerator, denominator :: (Integral a) => Ratio a -> a
この差はの理由はなんでしょう。直積形式の複素数はユニークに決定できます。 すなわち、:+ を含む同一性は明白です。一方、分数ではユニークに決 定できません。しかし、正規(規約)形式を持ちます。これは、この抽象データ型 の実装が保持すべきものです。たとえば、numerator (x%y) は x に等しいとはかぎりません。しかし、x:+y の実数部は常 に x です。
標準プレリュードと標準ライブラリには明示的な型変換をおこなう多重定義関数
がいくつか備わっています。
fromInteger :: (Num a) => Integer -> a
fromRational :: (Fractional a) => Rational -> a
toInteger :: (Integral a) => a -> Integer
toRational :: (RealFrac a) => a -> Rational
fromIntegral :: (Integral a, Num b) => a -> b
fromRealFrac :: (RealFrac a, Fractional b) => a -> b
fromIntegral = fromInteger . toInteger
fromRealFrac = fromRational . toRational
これらのうちふたつは、多重定義数値リテラルを実現するために、暗黙のうちに
使用されています。(小数を含まない)整数は実際には、Integer のよ
うな数値に fromInteger を適用したものと同等です。おなじように、
浮動小数は Rational のような数値に fromRationa を適用
したものとみなします。ですから、7 の型は
(Num a) => a であり、7.3 の型は
(Fractional a) => a です。つまり、数値リテラ
ルは、たとえば、総称数値関数でもちいることができます。
halve :: (Fractional a) => a -> a
halve x = x * 0.5
数値のいくぶん間接的な多重定義の方法には、数値を与えられた型の数値として
解釈するためのメソッドを、Integral あるいは Fractional
のインスタンス宣言によって指定できるという利点があります。(これが可能な
のは、fromInteger と fromRational はそれぞれ対応するク
ラスの演算子であるからです。) たとえば、Num のインスタンスであ
る (RealFloat a) => Complex a は次のメソッ
ドを含んでいます。
fromInteger x = fromInteger x :+ 0
これは、つまり、fromInteger の Complex 版インスタンス
は、実数部が適切な fromInteger の RealFloat 版インスタ
ンスにより生成される、複素数を生成するために定義されたものである、という
ことです。この伝でいけば、ユーザ定義の数値型(例えば、4 元数)でさえも多重
定義の数を利用することができます。
もうひとつ例をあげましょう。2
節の inc の最初の定義を思い出してください。
inc :: Integer -> Integer
inc n = n+1
型シグネチャを無視すれば、もっとも一般的な inc の型は
(Num a) => a->a です。しかし、この明示的な
型シグネチャは正当なものです。それは、これが主型より限定的である
からに他なりません。(主型より一般的な型では静的エラーが発生します。) こ
の型シグネチャには、inc の型を限定するという効果があります。そ
して、この場合には、inc (1::Float) のようにすると型付け不
可能ということになります。
以下のような関数の定義を考えてみてください。
rms :: (Floating a) => a -> a -> a
rms x y = sqrt ((x^2 + y^2) * 0.5)
この指数関数 (^) (3 つある、それぞれ別の型付けがされている標
準の指数関数のうちのひとつ、これについてはレポートの 6.8.5 節を参照のこ
と)の型は
(Num a, Integral b) => a -> b -> a
です。また、2 は (Num a) => a という
型ですから、x^2 の型は
(Num a, Integral b) => a となります。
これには問題があります。それは、b という型変数についての多重定
義を解決するほうほうがないということです。なぜなら、これは文脈のなかにあ
りますが、しかし、型式からは消えてしまっているからです。根本的には、プロ
グラマは、x は自乗するよう指定したのであって、2 が Int
の値であるか Integer の値であるかを指定したわけではありません。
もちろん、この問題は解決するこができます。
rms x y = sqrt ((x ^ (2::Integer) + y ^ (2::Integer)) * 0.5)
しかしながら、これは、すぐに面倒になるのは明白です。
実際、このての多重定義の曖昧性は数値に限ったことではありません。
show (read "xyz")
この文字列はどのような型だと仮定して読めばいいのでしょうか。これは指数関数の
ときの曖昧性よりもさらに深刻な問題です。なぜなら、先程の場合では、
Integral のインスタンスであればよいわけですが、今度の場合は
Text のどのインスタンスがこの曖昧性の解決に用いられるかによって
期待できる振舞いがすっかりかわってしまうからです。
この多重定義の曖昧性の問題は一般の場合と数値の場合ではちがってきますので、 Haskell では数値に限って解決法を提供しています。各モジュールにはデフォ ルト宣言 ( default declaration ) があり、これはキーワード default とそれにつづく括弧でくくられ、コンマで区切られた数値の モノタイプ (monotype) (型変数を含まない型) で構成されています。曖昧な型 変数がみつかった場合(たとえば、上例の b )、すくなくともクラスの ひとつが数値であり、かつ、そのクラスのすべてが標準のものであれば、デフォ ルトリストが参照されて、型変数の文脈を満足する最初のものが使用されます。 たとえば、もし、デフォルト宣言 default (Int, Float) があれば、上の曖昧な指数は型 Int と解決されるでしょう。(詳細に ついてはレポートの §4.3.4 を参照してくだ さい。)
「デフォルトのデフォルト」は (Integer, Double) ですが、 (Integer, Rational, Double) でもかまわないでしょう。 注意深いプログラマなら、default () を好むかもしれません。 これは、デフォルトがないということです。