Thursday 8 August 2013

Haskell 入門! (8)

今回はとうとう型の話だ!!!!!

en.wikibooks の Haskell より. CC-BY-SA での公開です.



Type Basics!!!!!!!!!!!
ウッヒョオオオオオオオオオオオオオオオオオオ type ダアアアアアアアアア
…コホン.
  • Haskell での型は大文字から始まる.String が string. 自分で型作るのもできるぜ
  • 例えば電話番号とかは,もちろん普通の数として扱ってもいいけど, TelephoneNumber みたいな型として見たほうが情報をたくさん得られたり,要らん混乱を弾いたりできそう.
  • ghci で :t ないし :type コマンド使ったらどの type か教えてくれるぜ!

というわけで :t やってみるぞー!!

いろんな値
Prelude> :t True
True :: Bool
Prelude> :t (3 < 2)
(3 < 2) :: Bool
Prelude> :t 'a'
'a' :: Char
Prelude> :t "a"
"a" :: [Char]
Prelude> :t 'ab'

<interactive>:1:1:
    Syntax error on 'ab'
    Perhaps you intended to use -XTemplateHaskell
Prelude> :t "ab"
"ab" :: [Char]
Prelude> :t 31
31 :: Num a => a
Prelude> :t 3.2
3.2 :: Fractional a => a
Prelude> :t (2/3)
(2/3) :: Fractional a => a
Fractional a => aとかはあと(次次回くらい?)で.
a は Fractional な何かの型やでっていう束縛みたいなもの.
Bool とかはご覧の通り,Char もそのまま文字型,
String[Char] のこと(どっちで書いてもいい)で,
[Char]Char の list のこと.list 自体は次回.


関数の型がどう表されるか.
関数の型って,(型)から(型)への写像,みたいな言い方のこと.
一般には甲 :: (引数1の型)->(引数2)->..->(引数n)->(返り値) と表す.
今度やるけど, list を受け取ったりするときには別の書き方がある,というか
リストをひとつの引数としてこういう書き方をするので,ご注意だ.
Prelude> :t not
not :: Bool -> Bool
Prelude> :t (+)
(+) :: Num a => a -> a -> a
Prelude> :t (/)
(/) :: Fractional a => a -> a -> a
Prelude> :t (^)
(^) :: (Integral b, Num a) => a -> b -> a
Prelude> :t (**)
(**) :: Floating a => a -> a -> a
Prelude> :t ^ --ちなみにこれは
<interactive>:1:1: parse error on input `^'
Prelude> let areaRect x y = x * y
Prelude> :t areaRect -- 自分では型指定してないけど推論してくれてる
areaRect :: Num a => a -> a -> a
アレやで, 1 + とかも関数やで
Prelude> :t (3<)
(3<) :: (Num a, Ord a) => a -> Bool
Prelude> :t (1+)
(1+) :: Num a => a -> a
この :: は type signature と言われ,自分で型宣言するときもこうすればよい.

a :: Integer
a = 3
mynot :: Bool -> Bool
mynot arg
    | arg        = False
    | otherwise  = True
的な.複数同じ型を持ってる場合は a,b :: (type) ってまとめてかけるぜ.
具体例を考えてみようか.chrord, ASCII な感じの番号と文字の相互変換.
これを使うには Data.Char を import する.
import ってのはひとまとまりのいろんな関数とかを使えるように読み込むってことで,
ghci からなら
Prelude> :m Data.Char 
(または :module) で import して
Prelude Data.Char> chr 97
'a'
Prelude Data.Char> chr 96
'`'
Prelude Data.Char> ord 'm'
109
.hs 書くならこう書いておく:
import Data.Char
使うときはpython みたいに Data.Char.chr(97) とはしない.これの型は
chr :: Int -> Char
ord :: Char -> Int

xor とか書いてみるか.
xor p q = (p || q) && not (p && q)
Bool と Bool で Bool 返すので
Prelude> :l xor.hs 
[1 of 1] Compiling Main             ( xor.hs, interpreted )
Ok, modules loaded: Main.
*Main> :t xor
xor :: Bool -> Bool -> Bool
*Main> 

もうちょっと現実的っぽい例を.GUI で使うような,ウィンドウ開くやつ.
ちょっと簡略化してるけどイメージ的には
openWindow :: WindowTitle -> WindowSize -> Window
ね,分かりやすそうやん.とりあえずこれからも :t は常に使って行きましょう.
練習問題.それぞれ型はどうかけるでしょう.
  1. 関数 negate, Int を受け取ってその符号を逆にしたのを返す.negate 3 = -3, negate -3 = 3 など.
  2. 関数 (||).
  3. monthLength. うるう年かどうかの Bool と月を表す Int からその月の日の数を返す.
  4. f x y = not x && y
  5. g x = (2*x - 1)ˆ2. 数は Int.

Type Inference

type signature を僕らが書かなくても ghci とか ghc はわかってくれる,のは,
type inference, 型推論ってのをやってくれるから.流れはこんな感じ.
isL c = c == 'l'
type signature 無しに書いてみる.ちゃんとこれでも
:t isL
isL :: Char -> Bool
とわかってくれてるわけ.
  • まず 'l' は Char.
  • となるとそれと (==) で比べられるためには c は Char じゃないとね
  • 最後に帰ってくるのは Bool だよね
って感じですね.
とはいえコードの読みやすさとか,バグ取りのこととか考えると type signature は積極的にしていこう.
たとえば typo とかしても, type signature をしといたら弾けることも多いけど,
signature なかったらそのまま変な型推論して妙なところで error でるかもしれないよね.
それに関数名と type signature 見たらだいたい何するかわかったりもするし.
このへんの事のおかげで,Haskell では「コンパイルは通ったけど動かしてみたら変な感じ」ってことは少なくて,
コンパイルの段階で「なんか変ダヨー」ってなって,コンパイルさえ通ればまあちゃんと動くぜって感じみたい. すごい.

No comments:

Post a Comment