[Haskell] 平均を求めるaverage関数

[Haskell] 平均を求めるaverage関数

Haskellのお勉強

ハノイの塔ユークリッドの互除法エラトステネスの篩などのアルゴリズムをHaskellで実装したりしてきました。 なんとなくHaskellでの書き方が身についてきたような気がします。

最近これまたなんとなく統計学の本も読んでいて、数年前の勉強内容を思い出そうとしています。 その中で分散や標準偏差が初歩として出てきているのですが、これをHaskellで書いてみようと思いました。

が、分散や標準偏差を求めるには当然平均値を求めなければいけません。 Haskell側で用意されているものを使って出せばいいやと思っていたのですが、Haskellには平均を求める関数はないのでしょうか … 合計値を求める sum関数みたいに、数値をリストで渡してばしっと平均値を出してくれる、そんな関数を探しています。

探してもどうもないみたいなので自分で書いてみました。

Haskellで平均値を求める関数

main = do
    print $ average ([1..100] :: [Int])
    print $ average ([1..100] :: [Integer])
    print $ average ([1..100] :: [Double])
    print $ average ([1..100] :: [Float])

average :: (Real a) => [a] -> Double
average xs = (realToFrac $ sum xs) / fromIntegral (length xs)

プログラムの解説

処理自体は単純に要素の合計を要素数で割るだけでよいのですが、若干はまったのがHaskellにおける数値の型についてです。 整数だろうが少数だろうが求められるように
引数をジェネリックな型にし、なおかつ戻り値の型(平均値の型)をDouble型にしようと考えました。

最初はReal型ではなくNum型で受け、処理をしようとしたのですが、どうも型をDoubleに合わせることができませんでした。 そもそもHaskellには数として振る舞う型クラスがいくつかあり、その関係の理解がなかなかどうして難しいのです。

Haskellにおける数値を表す型

そもそもHaskellには数値を表す型が大きく分けて4つ(Int, Integer, Float, Double)あります。 Intは値の範囲([-2^29 .. 2^29-1])が決まっている整数値を表し、Integerは制限のない整数値を表します。 FloatとDoubleはそれぞれ単精度浮動小数点数、倍精度浮動小数点数を表します。

そしてHaskellにはこれらをインスタンスとする型クラスがあり、型クラス制約として指定することでジェネリックな型として処理を行えます。 型クラスには制約としてほかの型クラスへの依存関係?が定義されています。オブジェクト指向でいうところのインタフェイスでしょうか。 依存関係は
こちらのサイトの図が分かりやすかったです。

数として振る舞う型クラス

名前 インスタンス 制約
Num Int, Integer, Float, Double Eq, Show
Fractional Float, Double Num
Floating Float, Double Fractional
Real Int, Integer, Float, Double Num, Ord
Integral Int, Integer Real, Enum
RealFrac Float, Double Real
RealFloat Float, Double Real, Floating

この表からわかるのは、整数も小数も引き受けられるジェネリックな型として使用できそうなのは、NumとRealです。

Real型からFractional型への変換

今回戻り値の型をDoubleとしたいので、引数のリストを合計した結果がInt(Integer)であろうとDoubleとして扱うために型変換をしなければなりません。

realToFrac関数を使えば文字通り、Real型をFractional型に変換できます。
つまり小数(Float, Double)として振る舞う型への変換です。

整数型(Int, Integer)から小数型(Float, Double)への変換

整数型(Int, Integer)を小数型(Float, Double)へ変換したい場合、fromIntegral関数でNum型に変換してやればよいです。

結果 …

あとはHaskellがよろしく型推論してくれて、戻り値の型のDoubleに合わせてくれます。
おそらくsumの結果がReal型からFractional型(小数)に変換され、要素数がNum型に変換され、戻り値の型がDoubleで指定してあるので、Double型としてそれぞれ振る舞うよう型推論されている … という理解でよいのでしょうか。

いろいろな型が錯綜していますが、とりあえずどんな型でも平均値をDouble型で求めるという当初の目的は果たせました。
引数をNum型で受け、そのままNum型同士の割り算の結果を返すようにすると、aではなくDoubleをよこせとエラーを吐かれます。
おそらく実際的な小数への変換処理(realToFrac)をしていないため、Num型のままということなのでしょう。

Num型をDouble型へ変換できれば早いと思ったのですが、そういうわけにもいかないようです?

C#とかだと暗黙的型変換がありますが、Haskellだといちいち型の変換を気にしてやる必要があります。

Haskellカテゴリの最新記事