Haskell
TECOTECDay 23

Haskellをちょこっと紹介(フィボナッチ数列を書いてみる)

アドベントカレンダー12/23分の投稿です。
大幅に遅れてしまい結果的にトリになってしまいました。すみません
来年は、いろいろ投稿できるように頑張りたいです。

Haskellでフィボナッチ数列を書いてみたい。

この数年、関数型プログラミングというワードが、盛り上がりを見せています。
自分も、興味があったので、最初はScalaを勉強していましたが、
より純粋な関数型言語をさわってみたかったのでHaskellを触り始めました。

本当は「Haskellはいいぞ!!」って感じの記事を書きたかったんですが、まだまだそれを表現できるほど
マスターできてないので、今回は、紹介+HELLOWORLD+フィボナッチ数列のよくある入門編でお茶を濁したいと思いますw

Haskellとは

Wikipediaより引用

Haskell は高階関数や静的多相型付け、定義可能な演算子、例外処理といった多くの言語で採用されている現代的な機能に加え、パターンマッチングやカリー化、リスト内包表記、ガードといった多くの特徴的な機能を持っている。また、遅延評価や再帰的な関数や代数的データ型もサポートしているほか、独自の概念として圏論のアイデアを利用し参照透過性を壊すことなく副作用のある操作(例えば 代入、入出力、配列など)を実現するモナドを含む。このような機能の組み合わせにより、手続き型プログラミング言語では記述が複雑になるような処理がしばしば簡潔になるばかりではなく、必要に応じて手続き型プログラミングを利用できる。

いまだにモナドが何かまではわかってません。

変数の定義

num::Int  --整数を定義
num1::Int --整数を定義

num  = 233 --整数を代入
num1 = 1.2 --小数を代入
-- エラー
-- No instance for (Fractional Int) arising from the literal `1.2'
-- In the expression: 1.2
-- In an equation for `num1': num1 = 1.2

Haskellは型をもっているので、変数定義時に型を定義する必要があります。
(忘れても、ある程度は推測してくれます)

配列の定義

myList::[Int] --型を定義
myList = [1,2,3,4] 
myList!!2  --要素を取り出し
-- 3

-- haskellの文字列は文字の配列
myString::[Char]
myString = ['J','O','J','O']
-- JOJO

myString2::String
myString2 = "JOJO"
-- 比較するとTrueが返る
myString == myString2
-- True

haskell では要素自体にアクセスすることはあまりないらしい

配列の接続

myList :: [Int] --型を定義
myList  = [1,2,3,4] 
myList2 = [11,12,13,14] 
myList3 = myList  ++ myList2 -- 配列をつなぐ
myList4 = 1:myList   -- 配列の先頭に要素を追加

myList3
-- [1,2,3,4,11,12,13,14]
myList4
-- [1,1,2,3,4]

関数の定義

Haskellの関数の定義は
関数名 引数1 引数2 … = 具体的な内容

-- 初期値と配列をとって合計を返す関数
mySum :: Int-> [Int] -> Int  -- 関数名 :: 引数1の型 -> 引数2の型 -> 返り値の型
mySum first [] = first  -- パターンマッチングで、空の配列の場合の定義
mySum first (x:xs) = x + mySum first xs -- それ以外の配列の場合の定義


-- haskellの関数は基本カリー化されているので、1変数の関数

自分の知る限りではLoop処理はありません。

関数から、別の関数を定義。

mySum100 = mySum 100 -- 初期値が100で配列をとって合計を返す関数

:t mySum100 -- 定義の確認
--- mySum100 :: [Int] -> Int 
-- 整数配列を引数にして、整数を返す関数を定義。

mySum100 [1,2,3,4,5,6,7,8,9,10] 
-- 155

型変数を使い、型を抽象化

myHead :: [Int] -> Int -- 整数配列の先頭の値を取得
myHead (x:xs) = x

myHead [3, 2, 31, 2]
-- 3

myHead [1.2 ,2.3, 3.1]
-- エラー
-- No instance for (Fractional Int) arising from the literal `1.2'
-- In the expression: 1.2
-- In the first argument of `myHead', namely `[1.2, 2.3, 3.1]'
-- In the expression: myHead [1.2, 2.3, 3.1]


myHead2 :: [a] -> a  --型を抽象化し、様々な型を引数にとっても問題ないようにする
myHead2 (x:xs) = x

myHead2 [3, 2, 31, 2]
-- 3
myHead2 [1.2 ,2.3, 3.1]
-- 1.2

myHead2 "Haskell"
-- H

 遅延評価

Haskellは遅延評価を採用している言語なので、実際に使用されるまで、式が評価されない

1 `div` 0 -- 0で割ると、エラーになる


zerodiv=1 `div` 0 -- 定義時には、エラーにならない

print zerodiv  -- 表示時に値が評価されるのでエラーになる
divide by zero

myHead2 [1, zerodiv ] -- 1 `div` 0が評価される前に値が返るのでエラーにならない
-- 1

myHead2 [zerodiv ,23] -- 1 `div` 0が最初に評価されるのでエラーになる
-- divide by zero

フィボナッチ数列

上記のHaskellの知識をもとにして、プログラム入門で定番の
フィボナッチ数列をHaskellで書いてみる。

定義

f_n = \left\{
\begin{array}{ll}
0 & (n = 0) \\
1 & (n = 1) \\
f_{n-1} + f_{n-2} & (n \geq 1)
\end{array}
\right.

Haskellで記述1

これをHaskellで書いてみる

fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

-- 0~10項目まで求めてみる
map fib [0,1,2,3,4,5,6,7,8,9,10]  -- map は配列の要素に関数を適用させる関数
-- [0,1,1,2,3,5,8,13,21,34,55]

これはそのままフィボナッチ数列の定義を書いたに過ぎません。

Haskellで記述2

もう一つ、どこかで見た気持ちわるい書き方で書いてみる

fib2 = 0:1: zipWith (+) fib2 (tail fib2)
take 11 fib2
-- [0,1,1,2,3,5,8,13,21,34,55]

補足

-- zipWith  配列を各要素ごとに処理する関数
res1 = zipWith (*) [1,2,3,4] [10,11,12] -- 要素ごとに掛け算を行う
res1
-- [10,22,36]

-- tail 初めの要素以外を返す
res2 = tail res1 
res2
-- [22,36]

これは関数ではなく定数で、無限に続く配列
遅延評価が可能なHaskellだから、こういう記述方法が可能らしい
最初みたときはちんぷんかんぷんだったけど、今は何となく理解できているつもり。
これがわかるとHaskellの面白みがわかるかもしれないです。

最後に

適当にHaskellでのフィボナッチ数列の書き方をかいてみました。
関数型プログラムは、命令というよりは、やりたいことを宣言する。初めはSQLを書いてる感じになりました。
従来の手続き型言語と違う発想が求められたりして、それがまた楽しいです。

Haskellは純粋関数言語ですが、別に手続き型言語のような書き方ができないわけではありません。
来年はHaskellで何かWEBサービスを作ってみたいです。