Haskell入門(雑)その1

  • 5
    Like
  • 1
    Comment

この記事は「すごいHaskellをたのしく学ぼう!」という本を進めていく上で、自分が感じた事や基本的な文法などを非常に雑に書いたものだ。
自分も現在進行系ですごいH本を進めている最中なので、まとめ的な感じでメモ程度に捉えてもらえれば幸いだ。
すごいH本でいうと今回は1〜2章付近を範囲としている。

実行環境

  • ghci version 8.0.1

環境が入っていない場合、Macであれば、ターミナルからbrew install ghciを、UbuntuをOSとして使用しているなら、sudo apt-get install ghciとでもやってインストールしよう。

はじめに「主観的に感じたHaskellのエレガントさ」

「馬鹿野郎。ライブラリ使えよ!」とツッコミを受けるかもしれないが、以下のコードを見てくれ。

quicksort.c

void quicksort(unsigned long int numbers[],int left,int right){
    int pivot, l_hold, r_hold;
    l_hold = left;
    r_hold = right;
    pivot = numbers[left];
    while(left < right){
        while((numbers[right] >= pivot) && (left < right)) right--;
            if(left != right){
                numbers[left] = numbers[right];
                left++;
            }
            while((numbers[left] <= pivot) && (left < right)) left++;
            if(left != right){
                numbers[right] = numbers[left];
                right--;
            }
    }
    numbers[left] = pivot;
    pivot = left;
    left = l_hold;
    right = r_hold;
    if(left < pivot){
        quicksort(numbers, left, pivot-1);
    }
    if(right > pivot){
        quicksort(numbers, pivot+1,right);
    }
}

cでライブラリを使わずにクイックソートを書こうとすると、こんな感じになる。
(なお、何故にこんなファイルがあったかというと、色々とあって競プロで直書きするという愚行を行なった過去があるからであり、つっこまないで欲しい。)
これをHaskellで書くと下記のようになる。

quicksort.hs
quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) = 
    let equalOrSmaller = [a | a <- xs , a <= x]
        larger = [a | a <- xs , a > x]
    in quicksort equalOrSmaller ++ [x] ++ quicksort larger

おい、何だこれは。ktkr。ウェーイ。そう、こんだけ短く書けるのだ。初見の時は頭がおかしいのかと思った。すごいH本を進めているとわかってきたが、Haskellは長いコードほど文法的にも仕組み的にも短く綺麗に書けるようになっていると感じた。「これが関数型言語か」とも思った。

Haskellの特徴(すごいH本より引用)

  • 純粋関数型言語
    • 関数ができることは何かを計算、処理し、その結果を返すことだけ。(「副作用を持たない。」と言う。)
    • 副作用を持たない性質から、同じ引数であれば同じ結果を必ず返すことが保証される。(参照透過性と呼ばれる。)
        
  • 遅延評価
    • 参照透過性が保証されているため、いつ計算するかを考える必要がない。
    • 上記の理由から、基本的に関数の出力結果が必要になるまで計算を行わない。
        
  • 静的型付け
    • コンパイラがどのコード片が数で、どれが文字か等を知っている。
    • これより、多くのエラーを詳しく捕まえてくれる。
        
  • 型推論
    • コード片に対して型を明示しなくても、Haskellの強力な型システムが推論してくれる。
    • 例 : a = 6 + 8というコードがあったとすると、aは数であるということを推測してくれるため、c言語みたいにIntだの何だのといちいち指定する必要がない。
    • こういった性質から汎用性の高いコードが書きやすくなる。(らしい。)
        
  • エレガントであり簡潔にコードを書ける点
    • 高度な概念をふんだんに用いているため、命令型で書かれたものより短くなる。
    • よって、保守しやすい。

0.GHC対話モードの使い方とHoogle

実行環境の部分で述べたghciをインストール後、ターミナルにて、ghciとタイプすれば起動する。終了する際は:qとタイプすればよい。自分で書いたhsファイルを読み込む際は:l hsファイル名とすれば、そこで定義した関数が使えるようになる。なお、後述するが、:t 関数名とすると関数の引数、返り値の型の詳細が見る事ができる。(自分は結構使ってる。)

また、:tで分からなかったり、もっと細かいことが知りたければ、Hoogleで検索すれば、詳細が出てくる。

1.型と型クラス

前座(演算子、リスト)

基本的な演算子についてはこのページ、リストについてはこのページを見てもらえば良いかなと思うので省略。面倒い部分は他の有能サイトに任せるなんて口が裂けても言えな(ry....

明示的な型宣言

まず、試しに以下のようなhsファイルを作ってみよう。

baby.hs
doubleMe x = x + x
doubleUs x y = doubleMe x + doubleMe y

これをGHCの対話モード上で実行してみる。

Prelude> :l baby.hs
*Main> doubleMe 5
10
*Main> doubleMe 10
20
*Main> doubleUs 5 10
30
*Main>:t doubleMe
doubleMe :: Num a => a -> a
*Main> doubleUs
doubleUs :: Num a => a -> a -> a

関数doubleMeと関数doubleUsが何を行なっているは明らか。この定義した関数を:tを使ってチェックしてみると初めて見るものが出ている。
結論から述べるとaは型変数といい、Numは型クラスの一種である。
Haskellでは型の名前は絶対に大文字から始まる。(例:Int、Float) 型変数というのはどんな型でも取り得ることを意味する。ちなみに、型変数を使っている関数を多相的関数と呼ぶ。
型クラスとは振る舞いを定義するインターフェイスである。ある型クラスのインスタンスである型はその型クラスが記述する振る舞いを実装する。
「::」という記号は「の型を持つ。」という意味がある。
「=>」という記号以前に書かれている部分(ここでは「Num a」がそれに該当。)は型クラス制約と呼ばれる。
これらを踏まえた上で関数doubleUsの「doubleUs :: Num a => a -> a -> a」を読み解くと、「関数doubleUsは同じ型の2つの引数を持ち、返り値として、引数と同じ型の値を返す。それらの型は全てNumクラスのインスタンスでなければならない。」と読める。

型クラス

Eq型クラス

Prelude>:t (==)
(==) :: Eq a => a -> a -> Bool
Prelude> :t (/=)
(/=) :: Eq a => a -> a -> Bool
  • 同じ型の引数を2つ取り、Bool値を返す。
  • 等値性をテストできる型に使われる。

Ord型クラス

  • 何らかの順序を付けられる型の為の型クラス。例として、大小比較演算子である(>)や(<)を見てみる。
Prelude> :t (>)
(>) :: Ord a => a -> a -> Bool
Prelude> :t (<)
(<) :: Ord a => a -> a -> Bool
  • 同じ型の2つの引数を取り、Bool値を返す。
  • 大小比較演算子(関数)をサポートする。

Show型クラス

  • ある値の型がshow型クラスのインスタンスになっている場合、文字列として表現できる。(例:関数show)
Prelude> :t show
show :: Show a => a -> String

Read型クラス

  • Show型クラスとは対をなす型クラス。
  • Read型クラスを使っているread関数の例は以下。
Prelude> :t read
read :: Read a => String -> a
Prelude> read "8.2" + 3.8
12.0
Prelude> read "5" - 1
4
Prelude> read "[2,3,4]" ++ [3]
[2,3,4,3]
  • ただ、read関数は文字列の引数1つを与えて、任意の型の値を返す。つまり、以下のような例では動かない。
Prelude> read "5"
*** Exception: Prelude.read: no parse
  • 例として、「read "5" - 1」が何故動いていたかというとread "5"の結果の値に手が加えられていた為、型推論によって型を特定できたからである。
  • 以下のように返り値の型を明示してあげて動かすやり方もある。
Prelude> read "5" :: Int
5

Enum型クラス

  • Enumのインスタンスは順番に並んだ型、つまり、要素を列挙できる型である。
  • Enumのインスタンスの型は前者関数pred、後者関数succ等で利用されている。
Prelude> :t pred
pred :: Enum a => a -> a
Prelude> :t succ
succ :: Enum a => a -> a
Prelude> pred 10
9
Prelude> pred 9
8
Prelude> succ 10
11
Prelude> succ 9
10
  • Enumのインスタンスとしては、()、Bool、Char、Ordering、Int、Integer、Float、Doubleなどがある。

Bounded型クラス

  • Bounded型クラスのインスタンスの型は上限と下限を持つ。それぞれ、minBound関数、maxBounded関数で調べることができる。
Prelude> :t minBound
minBound :: Bounded a => a
Prelude> :t maxBound
maxBound :: Bounded a => a
Prelude> minBound :: Int --Int型の下限の値
-9223372036854775808
Prelude> maxBound :: Int --Int型の上限の値
9223372036854775807
  • 無論、minBound、maxBound関数は他の型の上限、下限もチェック可能。

Num型クラス

  • 数を示す型クラス。この型クラスのインスタンスは数のように振る舞う。
  • Numのインスタンスとして、Int、Integer、Float、Double等が挙げられる。
  • ある型をNumのインスタンスにするには、すでにその型がEqとShowのインスタンスである必要がある。(GHC7.4.1以降はこの制限は撤廃。)

Floating型クラス

  • この型クラスはFloatとDoubleを含み、浮動小数点に使う。

Integral型クラス

  • この型クラスも数の型クラス。Numは実数を全て含むのに対し、この型クラスは整数全体のみを含む。
  • Int、Integerを含む。

今回はここまで。

This post is the No.21 article of 信州大学 kstm Advent Calendar 2016