やさしい Haskell 入門
back next top


10  

Haskell にはさまざまな数値型があります。これらは Scheme [7] の数値型をもとにしており、 Scheme は Common Lisp [8] をもとにしていま す。(しかし、これらの言語は動的な型付けをおこないます。) 標準の型には、 固定長および任意倍長の整数、それぞれの整数で構成された比(分数)、単精度お よび倍精度の実数、複素数の浮動小数が含まれています。ここでは数値クラス構 造の基本的性格の概略を説明します。詳細についてはレポートの §6.3 節を参照してください。

10.1  数値クラスの構造

数値型クラス(クラス Num とその下にあるクラス)は Haskell の標準 クラスの多くを考慮しています。NumEq のサブクラスで すが、Ord のサブクラスではないこを注意しておきます。これは、順 序述語は複素数には適用できないことによります。しかしながら、 Num クラスのサブクラス RealOrd のサブクラ スです。

Num クラスはすべての数値型に共通するいくつかの基本演算を備えて います。これには、加算、減算、符号反転、乗算、絶対値が含まれています。

(+), (-), (*)           :: (Num a) => a -> a -> a
negate, abs             :: (Num a) => a -> a

[negate は Haskell では唯一採用された前置演算子 - (マイナス) を適用する関数です。これの呼出しを (-) と書くことはできません。 それは、(-) と書けば、減算関数になってしまうからです。そこで、 この nagate という名前をかわりに使うわけです。 たとえば、-x*ynegate (x*y) と同値です。 (前置のマイナスは中置のマイナスとおなじ優先度をもちますので、当然、 乗算より優先度は低いわけです。)]

Num には除算演算子がないことに注意してください。 2 つの別の種類の除算演算子がオーバラップしない 2 つの Num のサ ブクラスで定義されています。クラス Integral では全数 整数除算演算と剰余演算子が提供されています。Integral の標準インスタンスは Integer (範囲制限のない整数あるいは数学で いうところの整数、一般には「bignum」としてしられているもの) と Int (範囲限定のある、計算機上の整数、範囲は最低でも 29 bit の符 号付 2 進数)です。一部の Haskell ではこれらのほかに別の integral 型が提 供されています。IntegralNum の直接のサブクラスでは なく、Real のサブクラスであることに注目してください。その意味す るところは、ガウス整数を提供するつもりはないということです。

他のすべての数値型はどれも Fractional クラスに属します。このク ラスは普通の除算演算子 (/) を備えています。そのサブクラスには、 Floating があり、三角関数、対数関数、指数関数を備えています。

FractionnalReal のサブクラスである RealFrac は関数 properFraction を備えています。この関 数は数を整数部分と小数部分にわけます。また、それぞれ別のルールで整数に丸 める関数群もそなえています。

properFraction          :: (Fractional a, Integral b) => a -> (b,a)
truncate, round,
floor, ceiling:         :: (Fractional a, Integral b) => a -> b

RealFloatFloatingRealFrac のサブクラ スで浮動小数部への効率てきなアクセスのためにいくつかの特別な関数を備えて います。それは、exponentsignificand です。標準の型 FloatDoubleRealFloat クラスになります。

10.2  構造のある数値型

標準の型、IntIntegerFloatDouble はプリミティブです。その他はこれらからタイプ構築子により 構築されます。

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 クラスの分数クラスを生成します。( RationalRatio Integer の型シノニムです。) しかし、Ratio は抽象型構築子です。データ構築子 :+ のかわりに、分数はふたつの 整数からひとつの分数を形成する「%」関数をつかいます。パターンマッ チングのかわりに、構成要素を引出す関数が用意されています。

(%)                     :: (Integral a) => a -> a -> Ratio a
numerator, denominator  :: (Integral a) => Ratio a -> a

この差はの理由はなんでしょう。直積形式の複素数はユニークに決定できます。 すなわち、:+ を含む同一性は明白です。一方、分数ではユニークに決 定できません。しかし、正規(規約)形式を持ちます。これは、この抽象データ型 の実装が保持すべきものです。たとえば、numerator (x%y)x に等しいとはかぎりません。しかし、x:+y の実数部は常 に x です。

10.3  数値型変換と多重定義されたリテラル

標準プレリュードと標準ライブラリには明示的な型変換をおこなう多重定義関数 がいくつか備わっています。

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 のインスタンス宣言によって指定できるという利点があります。(これが可能な のは、fromIntegerfromRational はそれぞれ対応するク ラスの演算子であるからです。) たとえば、Num のインスタンスであ る (RealFloat a) => Complex a は次のメソッ ドを含んでいます。

fromInteger x           =  fromInteger x :+ 0

これは、つまり、fromIntegerComplex 版インスタンス は、実数部が適切な fromIntegerRealFloat 版インスタ ンスにより生成される、複素数を生成するために定義されたものである、という ことです。この伝でいけば、ユーザ定義の数値型(例えば、4 元数)でさえも多重 定義の数を利用することができます。

もうひとつ例をあげましょう。2 節の inc の最初の定義を思い出してください。

inc                     :: Integer -> Integer
inc n                   =  n+1

型シグネチャを無視すれば、もっとも一般的な inc の型は (Num a) => a->a です。しかし、この明示的な 型シグネチャは正当なものです。それは、これが主型より限定的である からに他なりません。(主型より一般的な型では静的エラーが発生します。) こ の型シグネチャには、inc の型を限定するという効果があります。そ して、この場合には、inc (1::Float) のようにすると型付け不 可能ということになります。

10.4  デフォルトの数値型

以下のような関数の定義を考えてみてください。

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 () を好むかもしれません。 これは、デフォルトがないということです。


A Gentle Introduction to Haskell, Version 98
back next top