北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

GHCi上でpiと打てば円周率が表示されるという話

裏書きに残りっぱなしになってたのを書いておく。GHCI上で pi と打つと、πの値が表示できる。

Prelude> pi
3.141592653589793

Pythonだとそうはいかない。

>>> pi
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'pi' is not defined
>>> from math import pi
>>> pi
3.141592653589793

つまり、Haskellではグローバルな名前空間pi が出ているように見えるということ。これはなかなか気持ち悪い。

pi の正体を見てみよう。

Prelude> :i pi
class Fractional a => Floating a where
  pi :: a
  ...
        -- Defined in ‘GHC.Float’

Floating 型クラスに定義されているようだ。 Floating 型クラスは Prelude モジュールに含まれているので、 import などしなくても名前にアクセスできる。なるほど。

さて、拙著 Haskell入門 にも書いたが、この型クラスは浮動小数点数を意味する。この型クラスには pi 以外に、一般的な数値計算で必要な explogsin などの関数も入っている。代表的なインスタンスDoubleFloat

Prelude> :i Floating
class Fractional a => Floating a where
  pi :: a
  exp :: a -> a
  log :: a -> a
  sqrt :: a -> a
  (**) :: a -> a -> a
  logBase :: a -> a -> a
  sin :: a -> a
  cos :: a -> a
  tan :: a -> a
  asin :: a -> a
  acos :: a -> a
  atan :: a -> a
  sinh :: a -> a
  cosh :: a -> a
  tanh :: a -> a
  asinh :: a -> a
  acosh :: a -> a
  atanh :: a -> a
  GHC.Float.log1p :: a -> a
  GHC.Float.expm1 :: a -> a
  GHC.Float.log1pexp :: a -> a
  GHC.Float.log1mexp :: a -> a
  {-# MINIMAL pi, exp, log, sin, cos, asin, acos, atan, sinh, cosh,
              asinh, acosh, atanh #-}
        -- Defined in ‘GHC.Float’
instance Floating Float -- Defined in ‘GHC.Float’
instance Floating Double -- Defined in ‘GHC.Float’

pi が定数ではなく型クラスのメソッドになっているのは、恐らく FloatDouble で多相的に扱いたいからであろう。 GHCの実際の定義 は、以下。お、おう、といった感想。浮動小数点数だから、リテラルに小数点を何桁まで書いたかは精度とは関係ないってことだろう。結局は親の型クラスが持つ fromRational を使って処理される。

instance  Floating Float  where
    pi                  =  3.141592653589793238

instance  Floating Double  where
    pi                  =  3.141592653589793238

一応、ghci上で違いを見ておく。

Prelude> pi :: Double
3.141592653589793
Prelude> pi :: Float
3.1415927

もう一つ疑問が残るのは、数ある数値系の型クラスの中で、なぜ Floating というより具体性の高い型クラスに pi が定義されたのか。 Floating に定義されている関数は、実数値上に定義される関数である。Haskellでは、有理数Fractional 型クラスで表現されている。しかし、当然ながら実数値を正確に表現できる型はない。そこで、コンピュータが一般的に用いる浮動小数点数にこれらの関数を定義した、ということだろう。

最後にまとめると、 pi については以下となる。

  • Prelude に定義されているので、識別子 pi でアクセスできる
  • メソッドになっているため、 FloatDouble で多相的に扱える
  • 実数という型がないので、浮動小数点数上の演算としているのだろう