訳者まえがき
Haskellには初心者殺しの関所が幾つもありますが、その一つに
- 簡単な四則演算すら型が合わずコンパイルできない
があります。
この問題を解決するためにアチコチの本やサイトを読みましたが、結局一番分かりやすかったのはwiki.haskell.orgの記事でした。
自分のために翻訳したものを公開します(最後に独自に数値型の階層と変換関数の一覧もまとめています)。
日本語版公開にあたってはHaskellWiki:Copyrightsを参照し、問題ないとの判断をしていますが、万が一権利侵害等がありましたら、ご指摘ください。許諾等の処理が完了するまで公開を中止します。
本文
以下はConverting numbersの日本語訳です。
Haskellにおける数値型間の変換は明示的に行う必要があります。
これは他の多くの伝統的言語(CやJavaなど)で暗黙にキャストが行われるのとは異なります。
1. 整数型との間の変換
整数(Integral
)型には整数だけが含まれます(分数は含まれません)
最も一般的に使われるのは次の二つの型です。
-
Integer
は他の言語ではしばしば「bignum」または「big-integer」と呼ばれる任意精度の整数です。 -
Int
は固定幅のマシン固有の整数で、最小保証範囲は-2^29〜2^29-1ですが、実際にはもっと大きくなることが多いです。GHCコンパイラのx86-64バージョンでは、符号付64ビット整数を扱うことができます。
整数型からの変換に主に使われるのはfromIntegral
です。
任意の整数型を任意の数字(Num
)型(Int
,Integer
,Rational
,Double
)に変換します。
fromIntegral :: (Num b, Integral a) => a -> b
例えばInt
である値n
のルートをとる場合、単純にsqrt n
とは書けません。なぜならsqrt
は浮動小数点数にのみ適用可能だからです。
したがって、代わりにsqrt (fromIntegral n)
と明示的にn
をNum
型に変換し、浮動小数点の値に降りていけるようにする必要があります。
fromInteger :: Num a => Integer -> a
toInteger:: Integral a => a -> Integer
2. 実数型と実数分数型(有理数型)間の変換
実数分数(RealFrac
tional)型は、整数と分数のどちらも属することができます。
最も一般的に使われる型は次のとおり。
-
Rational
は任意精度の分数 -
Double
は倍精度の浮動小数点数
実数(Real
)型は整数(Integral
)型と実数分数(RealFrac)型の両方を含みます。
型名の"real"は複素数が除外されていることを示しています。
実数(Real
)型からの変換に使われるのはrealToFrac
で、任意の実数型を任意の分数型(Fractional
)型(Rational,Double
の属する型)に変換します。
realToFrac:: (Real a, Fractional b) => a -> b
realToFrac
はRealFrac
型間の変換にも使うことができます。
(後述のとおり、realToFrac
を浮動小数点型間の変換に使うことは避けてください)
fromRational :: Fractional a => Rational -> a
toRational :: Real a => a -> Rational
3. 実数分数から整数への変換
整数型は非整数を表すことができないので、これは本質的に損失のある変換です。 変換方法に応じて、次のいずれかを選択することができます:
ceiling :: (RealFrac a, Integral b) => a -> b
floor :: (RealFrac a, Integral b) => a -> b
truncate :: (RealFrac a, Integral b) => a -> b
round :: (RealFrac a, Integral b) => a -> b
4. 異なる精度の浮動小数点間の変換
Float
とDouble
の間の変換は、GHC.Float
モジュールのGHC固有の関数を使用します。
float2Double :: Float -> Double
double2Float :: Double -> Float
RealToFrac
を使用して浮動小数点型同志の変換は避けてください。中間型であるRational
は、無限大またはNaNなどの例外的な値を表すことができません。 GHCチケット#3676を参照してください。
5. 自動変換
人々は何度も数値間の自動変換を要求しますが、これは通常良くない考えとされています。詳細は一般的な数値型に関する考えを参照してください。
6. 例
2つの座標系の間で変換するいくつかの関数を記述しようとしています。
coord1
を呼び出す最初の座標系は、左上が(0、0)
で始まり、右下が(500,500)
で終わります。
coord1
の座標は(Int、Int)
型です。
coord2
と呼ばれる第2の座標系は、左下が(0.0、0.0)
で始まり、右上の(1.0,1.0)
で終わります。
coord2
の座標は、(Float、Float)
型です。
型チェックが通るように以下の2つの関数を書き換えます。
coord1ToCoord2 :: (Int, Int) -> (Float, Float)
coord1ToCoord2 (x, y) = (x/500, (500-y)/500)
coord2ToCoord1 :: (Float, Float) -> (Int, Int)
coord2ToCoord1 (x, y) = (500/(1/x), 500 - 500/(1/y))
coord1ToCoord2 (0, 0) -> (0.0, 1.0)
coord1ToCoord2 (250, 250) -> (0.5, 0.5)
coord1ToCoord2 (350, 350) -> (0.7, 0.3)
coord1ToCoord2 (500, 500) -> (1.0, 0.0)
coord2ToCoord1 (0.0, 0.0) -> (0, 500)
coord2ToCoord1 (0.5, 0.5) -> (250, 250)
coord2ToCoord1 (0.7, 0.7) -> (350, 150)
coord2ToCoord1 (1.0, 1.0) -> (500, 0)
解法はfromIntegral
とround
を使うことです。
coord1ToCoord2 :: (Int, Int) -> (Float, Float)
coord1ToCoord2 (x, y) = (fromIntegral x/500, (500 - fromIntegral y)/500)
coord2ToCoord1 :: (Float, Float) -> (Int, Int)
coord2ToCoord1 (x, y) = (round (500 * x), round (500 - 500 * y))
訳者あとがき
特にクラス名の訳語は最適なものが見つからないので、標準的な訳語があるものは教えていただきたいです。
この記事の理解を深めるため、私の書いた図を二つ付けておきます。(かなり昔に作ったため、陳腐化や間違いがあればご指摘くださると助かります)
a. 数値クラスの階層関係
参考:hackage , Haskell Language Report 2010
b. 数値型間の変換表
参考:「Real World Haskell」6章 6.4.4数値型 表6-4