Nabe
Twitter: @nabe256
Haskell
Haskell は高階関数や静的多相型付け、定義可能な演算子、例外処理といった多くの言語で採用されている現代的な機能に加え、パターンマッチングやカリー化、リスト内包表記、ガードといった多くの特徴的な機能を持っている。また、遅延評価や再帰的な関数や代数的データ型もサポートしているほか、独自の概念として圏論のアイデアを利用し参照透過性を壊すことなく副作用のある操作(例えば 代入、入出力、配列など)を実現するモナドを含む。
(by Wikipedia)
一言で言うと
関数型言語
です。
ちょっとだけ詳しく言うと
純粋関数型
プログラミング言語
です。
手続き型言語
› 記述された命令を逐次実行していく。
› 一般的に用いられる言語。
関数型言語
› すべての計算は関数の評価で行われる。
› 変数が無く、定数しか無い。
› 手続き型でのループは再帰で実現する他、
様々な機能が別の形で実現されている。
その他の種類について興味のある方は
「プログラミングパラダイム」という
キーワードで調べると良いでしょう。
基本的に副作用が無いため、
原因不明のバグが出ることが少ない。
どういう物が必要かを記述していけば
書けてしまうので、短時間での開発が可能。
クイックソートの例
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = [a | a <- xs, a <= x]
larger = [b | b <- xs, b > x]
非常に簡潔に記述出来る。
代表的なもの
› 再帰関数(自分自身を呼び出す関数)
› λ式(ラムダ式、無名関数)
› 無限リスト(長さが無限のデータ)
› 遅延評価 (必要な時まで式を評価しない)
› 高階関数(関数の引数または返り値が関数)
› 純粋 (基本的に副作用が無い)
› モナド(副作用を扱う仕組み)
他多数!
Haskellが使用されているプロジェクト › Pugs
HaskellによるPerl6の実装
› darcs 分散バージョン管理システム
› Whitespace タブ・空白・改行のみで記述する言語
› Monadius グラディウス風のゲーム
› Mighttpd (発音:Mighty) HTTPサーバ
基本的な文法をいくつか覚える。
簡単なプログラムを書けるように
なれれば素敵。
GHC (Glasgow Haskell Compiler)
Haskellにおける事実上の標準コンパイラ。
実行が高速で、ライブラリ多数。
Hugs
軽量なコンパイラ、基礎を学ぶには十分。
導入も簡単だが開発は停止している模様。
Haskell Platform
Haskell処理系のGHCに加えて
各種ツールやライブラリが揃っている。
UNIXであれば
GHCか、可能であればHaskell Platformを。
Windowsであれば
Haskell Platformが最適。
高度な事をするのであれば、
UNIXならGCC
WindowsならMinGW
などが必要。
今回はGHCに付属する
› コンパイラのGHC
› インタプリタのGHCI
を使用します。
環境がある方は挑戦してみましょう。
まずはインタプリタから
実行してみます。
Win/UNIXならghci、MinGWならghcii.shを起動します。
$ ghci
GHCi, version 7.0.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading package ffi-1.0 ... linking ... done.
Prelude>
Prelude(標準ライブラリ)が出てくれば準備完了です。
:quit または :q で終了します。 Prelude> :q
Leaving GHCi.
$
いくつか試してみましょう
> 256
256
> 1+2*3
7
> 7/2
3.5
> 2^256
結合順位もしっかりあります
> 8/4/2
1.0
> 2^3^2
512
> (2^3)^2
64
:typeで型が見られます。(:t と略せる)
> :type 256
256 :: Num a => a
> :t 1+2
1+2 :: Num a => a
> :t 3.5
3.5 :: Fractional a => a
> :t 7/2
関数は :info で見ると色々情報が出ます。
(:i と略せる)
> :info (+)
class (Eq a, Show a) => Num a where
(+) :: a -> a -> a
...
-- Defined in GHC.Num
infixl 6 +
Haskellでは型が非常に重要な要素です。
型が分かればHaskellを理解する
良い手がかりになります。
型の詳細は後ほど。
文字関係です。
> „a‟
„a‟
> “Hello”
Hello
リストを使ってみます。
> [1,2,3]
[1,2,3]
> head [1,2,3]
1
> tail [1,2,3]
[2,3]
リストは色々な操作が可能です。
> [1,2,3,4,5] !! 2
3
> take 3 [1,2,3,4,5]
[1,2,3]
> drop 3 [1,2,3,4,5]
[4,5]
> reverse [1,2,3,4,5]
[5,4,3,2,1]
長さも変更可能です。
> length [1,2,3,4,5]
5
> length (tail [1,2,3,4,5])
4
> [1,2,3] ++ [4,5]
[1,2,3,4,5]
: 演算子(cons)
> 1:2
[1,2]
> 1:[2,3]
[1,2,3]
同じ型式でないといけません。
以下はエラーが発生します。
> [1,„a‟]
> [1,“Hello”]
ちなみに、文字と文字列の関係
> [„a‟,„b‟,„c‟]
“abc”
文字列は文字のリスト、ということです。
タプルを使ってみます。
> (1,2)
(1,2)
> (1,2,3)
(1,2,3)
長さを変えられない代わりに、
違う型でも使えます。
> (1,‟a‟)
(1,‟a‟)
> (1,”Hello”)
(1,”Hello”)
> (1,'a',"hello",[1,2,3])
(1,'a',"hello",[1,2,3])
タプルを使った関数。
> fst (1,”hello”)
1
> snd (1,”hello”)
”hello”
Prelude> putStrLn “Hello, World!”
Hello, World!
Hello, World! と出力してくれる
おなじみのサンプルです。
putStrLn “Hello, World!”
関数を適用する場合は
空白文字を使います。
Prelude> take 3 [1,2,3,4,5]
[1,2,3]
2引数の関数です。
リストから指定した数の要素を
取り出します。
2つ以上の引数に対して関数適用する場合も空白文字を使います。
Prelude> take 3 (tail [1,2,3,4,5])
[2,3,4]
引数に関数を渡す場合は括弧を使います。
関数を定義して実行してみましょう。
$ ghci
Prelude> let main = putStrLn “Hello, World!”
等式の左辺が関数名、
右辺は関数の内容になります。
let 関数名=関数の内容
という形式です。
先ほど定義したmainを使用してみます。
Prelude> main
Hello, World!
実行出来ました。
先ほどはインタプリタで実行しました。
ならば今度は
コンパイルして実行してみましょう。
以下の行をhello.hsというファイルに 保存します。
main = putStrLn “Hello, World!”
関数名=関数の内容という形式です。 先程と同様に等式の左辺が関数名、 右辺は関数の内容になります。
こちらが一般的な書き方です。 先ほどのletはインタプリタ用と考えてください。
hello.hsをコマンドラインから コンパイルして実行してみます。
$ ghc --make hello.hs
$ ./hello
Hello, World!
$
実行できました。
実際にHaskellを扱うに当たって
コードを記述して実行する方法は
いくつかあります。
先程の例
› インタプリタ上で記述して実行
› ソースをコンパイルして実行
実際にHaskellを扱うに当たって
コードを記述して実行する方法は
いくつかあります。
その他
› ソースをインタプリタに渡して実行
› ソースをインタプリタから読み込んで実行
ソースをインタプリタに渡して実行
$ ghci hello.hs
Ok, modules loaded: Main.
Prelude Main> main
Hello, World!
Prelude Main> :q
$
ソースをインタプリタから読み込んで実行
$ ghci
Prelude> :load hello.hs
Ok, modules loaded: Main.
Prelude Main> main
Hello, World!
Prelude Main> :q
$
コンパイルして実行する場合
› 高速に実行できる
› 実行するにはmainを定義する必要がある
インタプリタで実行する場合
› 便利な機能が沢山あるので
学習する時に非常に便利
› mainを定義しなくても良い
:reloadで再読み込みが出来る
先程のhello.hsを読み込む。
> :load hello.hs
> main
Hello, World!
GHCIを起動したまま、
別ウィンドウでhello.hsを編集する。
$ cat hello.hs
main = putStrLn “Hello, World!”
$ #編集
$ cat hello.hs
main = putStrLn “Hello, Haskell!”
GHCIのウィンドウに戻り、
再読み込み。
> :load hello.hs
> main
Hello, World!
> :reload
GHCIのウィンドウに戻り、
再読み込み。そして実行。
> :load hello.hs
> main
Hello, World!
> :reload
> main
Hello, Haskell!
インタプリタならではのやり方でした。
あとは :type や :info など。
調べるのに便利な機能が色々あります。
Haskellは型が重要。
型さえ覚えれば
第一の突破口クリア。
1引数の関数を見てみる
headを見てみる
> head [1,2,3]
1
> :t head
head :: [a] -> a
head :: [a] -> a
head :: [a] -> a
関数名
head :: [a] -> a
これ以降は型を表す
head :: [a] -> a
型
head :: [a] -> a
a型
任意の型
head :: [a] -> a
[a]型
任意のリスト型
head :: [a] -> a
[a] -> a型
[a]型からa型へと変換する型
head :: [a] -> a
[a] -> a型
[a]型からa型へと変換する関数
head :: [a] -> a
[a] -> a型
任意のリスト型から任意の型へと
変換する関数
a型を具体的な型に置き換えるため、
先程の例
> head [1,2,3]
1
を当てはめてみる。
head :: [a] -> a
[a] -> a型
任意のリスト型から任意の型へと
変換する関数
head :: [Num] -> Num
[Num] -> Num型
数値のリスト型から数値型へと
変換する関数
head :: [Num] -> Num
headは[Num] -> Num型
数値のリスト型から数値型へと
変換する関数
head :: [Num] -> Num
headは[Num] -> Num型
数値のリスト型から数値型へと
変換する関数
本当にそうなのか確認。
> head [1,2,3]
1
> head [1,2,3]
数値のリスト型
1
数値型
> head [1,2,3]
数値のリスト型
1
数値型
数値のリスト型から
数値型へと正しく変換されている。
1引数の関数を見てみる(2)
sumを見てみる
> sum [1,2,3,4,5]
15
> :t sum
sum :: Num a => [a] -> a
型の読み方
sum :: Num a => [a] -> a
型の読み方
sum :: Num a => [a] -> a
関数名
型の読み方
sum :: Num a => [a] -> a
sum関数の型
型の読み方
sum :: Num a => [a] -> a
[a]型をa型に変換する関数
型の読み方
sum :: Num a => [a] -> a
aを数値型とするクラス
型の読み方
sum :: Num a => [a] -> a
数値のリスト型を
数値型に変換する関数
型の読み方
sum :: Num a => [a] -> a
数値のリスト型を
数値型に変換する関数
本当にそうなのか確認
> sum [1,2,3,4,5]
15
> sum [1,2,3,4,5]
数値のリスト型
15
数値型
数値のリスト型から数値型へ
正しく変換されている
2引数の関数を見てみる(1)
takeを見てみる
> take 3 [1,2,3,4,5]
[1,2,3]
> :t take
take :: Int -> [a] -> [a]
take :: Int -> [a] -> [a]
take :: Int -> [a] -> [a]
関数名
take :: Int -> [a] -> [a]
型
take :: Int -> ([a] -> [a])
右結合なので
このように解釈する
take :: Int -> ([a] -> [a])
[a]型を受け取り
[a]型を返す関数
take :: Int -> ([a] -> [a])
関数A
take :: Int -> ([a] -> [a])
Int型を受け取り
関数Aを返す関数
take :: Int -> ([a] -> [a])
Int型を受け取り
関数A
を返す関数
take :: Int -> ([a] -> [a])
Int型を受け取り
([a]型を受け取り
[a]型を返す関数)
を返す関数
take :: Int -> [a] -> [a]
Int型を受け取り
[a]型を受け取り
[a]型を返す関数
を返す関数
take :: Int -> [a] -> [a]
Int型を受け取り
[a]型を受け取り
[a]型を返す関数
を返す関数
言い換えると
take :: Int -> [a] -> [a]
Int型と[a]型を受け取り
[a]型を返す関数
という事です
非常に分かりづらいので
順を追って解読してみる。
takeにInt型の値(2)を渡してみる
take :: Int -> [a] -> [a]
Int型を受け取り
[a]型を受け取り
[a]型を返す関数
を返す関数
takeにInt型の値(2)を渡してみる
(take 2) :: Int -> [a] -> [a]
Int型を受け取り
[a]型を受け取り
[a]型を返す関数
を返す関数
takeにInt型の値(2)を渡してみる
(take 2) :: Int -> [a] -> [a]
Int型を受け取り
[a]型を受け取り
[a]型を返す関数
を返す関数
takeにInt型の値(2)を渡してみる
(take 2) :: [a] -> [a]
[a]型を受け取り
[a]型を返す関数
(take 2)に[a]型の値([4,5,6])を渡してみる
(take 2) :: [a] -> [a]
[a]型を受け取り
[a]型を返す関数
(take 2)に[a]型の値([4,5,6])を渡してみる
(take 2 [4,5,6]) :: [a] -> [a]
[a]型を受け取り
[a]型を返す関数
(take 2)に[a]型の値([4,5,6])を渡してみる
(take 2 [4,5,6]) :: [a] -> [a]
[a]型を受け取り
[a]型を返す関数
(take 2)に[a]型の値([4,5,6])を渡してみる
(take 2 [4,5,6]) :: [a]
[a]型を返す関数
(take 2)に[a]型の値([4,5,6])を渡してみる
(take 2 [4,5,6]) = [4,5]
[a]型を返す関数
(take 2)に[a]型の値([4,5,6])を渡してみる
(take 2 [4,5,6]) = [4,5]
[4,5]を返す関数
(take 2)に[a]型の値([4,5,6])を渡してみる
(take 2 [4,5,6]) = [4,5]
結果 = [4,5]
本当にそうなのか確認
> take 2 [4,5,6]
[4,5]
> take 2 [4,5,6]
Int型
[4,5]
> take 2 [4,5,6]
Int型 [a]型
[4,5]
> take 2 [4,5,6]
Int型 [a]型
[4,5]
[a]型
> take 2 [4,5,6]
Int型 [a]型
[4,5]
[a]型
どうやら本当になったようです。
実際にGHCIでも確認してみましょう。
> :t take
take :: Int -> [a] -> [a]
> :t take 2 take 2 :: [a] -> [a]
> :t take 2 [4,5,6]
take 2 [4,5,6] :: Num a => [a]
> take 2 [4,5,6]
[4,5]
確かにそのように動作していました。
少々かみ砕きすぎたでしょうか。
余計難しくなったかも知れません。
一言で説明しても順を追って説明しても
難解な概念ですが、何度かソースを書いて
試して見ると、急にすっと分かるように
なります。
型を意識して作る必要があります。
型を考えるという事は
入力の形と出力の形を考える事、
そして関数の入出力と処理内容を
切り分ける事へと繋がります。
型の概念は難しい部分がありますが、
型が分かるようになると
難しい事を考えなくても
プログラムが書けるようになります。
全体的に駆け足で説明する事に
なってしまいました。
Haskellが分かるようになると
いつの間にか面白いと思うようになります。
ありがとうございました。