Sunday 11 August 2013

Haskell 入門! (11)

今回とうとう (Num a) => とか書かれてたアレの正体が明らかに.
そして数のいろんな型もお目見えします.
ちょっと長い目.

en.wikibooks の Haskell に基づいたものです. CC-BY-SA で配布.


polymorphic types

さて,
[True, False, True]
['a', 'b']
["hey", "may"]
それぞれ型が違います.最初のは [Bool], 続いて [Char], さらに [[Char]].
ほんで,これまでやってきた通り,関数が受け取れる型っていうのは予め決まってるわけ.
と,いうことは.例えば list の長さが欲しい時には,
lengthBools :: [Bool] -> Int とか lengthChars :: [Char] -> Int とか色々必要,
というのはあまりに面倒というか理不尽.実際にはひとつの関数 length が対応してくれるのでこれの型を見てみましょう.
Prelude> :t length
length :: [a] -> Int
この a っていうのは型じゃない(そもそも型は大文字から始まる).
これが type variable, 型変数と呼ばれるもので,ここにはどんな型でも入ってよい.
つまるところこの [a] という型表示は,Bool [[Char]] その他,何でもいいからそれのリスト,ってこと. こういうふうに複数の型を(同時にではないが)許容するのは type theory の用語で polymorphism というらしい.
形容詞系は polymorphic, 対義語は monomorphic.
ここで気をつけるべきは,ひとつの type signature の中では同じ型変数には同じ型が入るということ.
ふつうの変数と同じですね.例えば,
f :: a -> a
という時にはなんの型でもいいから受け取って同じ型で返す関数を表わしてて,
f :: a -> b
という時には ab は同じかもしれないし違うかもしれない.
というわけで早速具体例を見てみよう.そろそろ何を見ても「型はどうなってるかな」と気になる頃. さっきやった fstsnd はどうなるだろうか(もちろんまず自分で考えて).
Prelude> :t fst
fst :: (a, b) -> a
Prelude> :t snd
snd :: (a, b) -> b
すっきり.headtail とかも各自.
練習問題
  1. 前の
    1. ( ("Hello", 4), True) から 4 を取り出してみよう.
    2. tuple で list の 頭と残りを返す関数書いてみよう.
    の型
  2. h x y z = chr (x-2) の型は? (試すときは:mimport 忘れずに)

練習問題のあれ
Prelude> :m Data.Char
Prelude Data.Char> let h x y z =  chr (x-2)
Prelude Data.Char> :t h
h :: Int -> t -> t1 -> Char
この t とか t1 とかでどのアルファベットを選ぶかってなんか決まりあるんだろうか.

Type Basics II

Typeclass

さてさて,これまでぼんやりとしか喋ってこなかったけれども,例えば (+) の type はどうあるべきか.
アレやん, Int 同士の足し算ならいいけどさ, Float とかも自在に扱って欲しいわけ.
かといって CharInt を勝手に足されても困る.
たとえば Number みたいな型があって,それで数全部を扱ってしまえれば嬉しい:
(+) :: Number -> Number -> Number
せやけど,例えば浮動小数点型と整数型は区別せな色々困るわけで,これはやっぱり望み薄.
先程の type variable の概念を援用し,たいところだが,そのままだと数以外のものの足し算も許容してしまう.
というわけで… clever にも,「数を表す型ならなんでもおk」な型変数みたいな指定ができるのです!
(+) :: (Num a) => a -> a -> a
この Num っていうのが typeclass と言われるもので,Num a => の部分で制約をかけているわけ.
かっこ良くいうと aNum の instance に制限する,ということらしい.

Numeric Types

というわけで数を表す型にどんなものがあるか.
  • Int. 精度の固定されてるよくある整数型.少なくとも [-2^29 .. 2^29-1] を扱えるらしい.
  • Integer. 任意精度の整数(メモリのある限り数え続けてくれる).python でいう Long Integer.
  • Float. 単精度浮動小数点型.特段事情がなければ Double 使いましょう.
  • Double. 倍精度浮動小数点型.小数というか実数扱うならだいたいこれのお世話になる.

この光のもとで足し算とかそのへんを見なおしてみる.
先ほど申し上げた通り,
(+) :: (Num a) => a -> a -> a
というわけですが,では例えば
3 + 2.5
これ整数 (Int とか Integer とか) と 非整数 (Float or Double) の足し算にみえるけど.
でも2つの型が違うと困る.上の a -> a -> a が困る.
実は:
Prelude> :t 3
3 :: Num a => a
3 自体はまだ Int でも Integer でも Double でも無いわけですねー. じゃあ 2.5 はっと
Prelude> :t 2.5
2.5 :: Fractional a => a
ほみほみ.なんか Real とか Eq とか色々あるみたいやけど,今は深入りはやめとこう.
少なくとも FractionalNum で, IntIntegralFractional じゃないです.
Haskell さんの解釈としては,まず制約のきつい 2.5 の方を考えて,
特に他に情報はないのでデフォルトの Double として処理し,その後全体によきにはからう.
結局(この場合)計算全体は Double で進行して Double で帰ってくる.
これ,型キャストとも雰囲気が似ているが,Haskell では明示的に polymorphic なものにしか行われない.

さてさて.
Prelude> :t (/)
(/) :: Fractional a => a -> a -> a 
Fractional への制限は,python2.x みたいに 3/21 にはなってほしくないので必要だけど,
たとえばはっきり Int やでって宣言してるような奴
Prelude> :t length 
length :: [a] -> Int
とかでやろうとしてみると
Prelude> 4 / length [1,2]

<interactive>:2:3:
    No instance for (Fractional Int) arising from a use of `/'
    Possible fix: add an instance declaration for (Fractional Int)
    In the expression: 4 / length [1, 2]
    In an equation for `it': it = 4 / length [1, 2]
polymorphism が型キャストと違うというのはこういうところ.
なおこの場合は便利な関数があって,
fromIntegral :: (Integral a, Num b) => a -> b
見ての通り.
Prelude> 4 / fromIntegral(length [1,2])
2.0
Prelude> 4 / fromIntegral(length [1,2,3])
1.3333333333333333
めでたし.かな.
ついでに補足.
(==) :: (Eq a) => a -> a -> Bool
この Eq は正味比較して等しいかどうかを判断できるもの,みたいなアレ.non-functional なのはだいたいこれらしい.
typeclass のもっとすごい力とか,カスタマイズの仕方とかは,後々出てくるのでお待ちください.

No comments:

Post a Comment