操作方法

Haskell最初の一歩

2008/03/09
MAS

目次

Haskellとは

非正格な動作を持つ純粋関数型プログラミング言語である。 Haskell - Wikipedia

→詳しくはWebで(調べてね)

Haskellすごい

ループ→再帰

突然ですが問題です

Perl版(ループ処理)

my @data = (1, 5, 3, 7, 1, 9, 2);
my $total = 0;
foreach my $val (@data) {
    $total += $val;
}
print $total, "\n";

Haskell版(再帰処理)

main = do print $ total [1,5,3,7,1,9,2]

total [] = 0
total (x:xs) = x + total(xs)

total の中から total を呼び出しています

total = 先頭の一要素 + 残りの要素のtotal

(x には先頭の一要素 xs には残りの要素が入ります)

再帰のイメージ

→要は下請けにほとんど丸投げ

再帰のイメージ(続き)

というのはあくまでもイメージなので……

→真面目な説明はWebで(調べてね)

じゃあ、ループは?

ちょっと脱線

main = do print $ total [1,5,3,7,1,9,2]
main = do print(total [1,5,3,7,1,9,2])

リスト処理

では、次の問題です

もうおわかりですね

main = do print $ add3 [1, 5, 3]

add3 [] = []
add3 (x:xs) = (x + 3) : add3(xs)

: はリストの先頭に要素を追加する演算子です

1 : [2, 3] → [1, 2, 3]

こうやって書くこともできます

main = do print $ map plus3 [1, 5, 3]

plus3 n = n + 3

map登場

map 関数 リスト

という形で使います。

map plus3 [1, 5, 3] → [plus3(1), plus3(5), plus3(3)]

というように、リストの各要素に関数を適用したリストを返します。

注目:引数に関数を指定することができます。
(詳しくは、高階関数でググってみよう)

FizzBuzzなんて余裕

1 から順に数を数えていく。但し、その数が 3 で割り切れるならば数字の代わりに Fizz と、5 で割り切れるなら Buzz と言うゲーム。3 でも 5 でも割り切れる場合は、FizzBuzz の順に言う。 FizzBuzzとは - はてなダイアリー
main = do print $ map fizzBuzz [1..100]

fizzBuzz n
  | n `mod` 15 == 0 = "FizzBuzz" -- 3でも5でも割り切れる
  | n `mod`  5 == 0 = "Buzz"     -- 5で割り切れる
  | n `mod`  3 == 0 = "Fizz"     -- 3で割り切れる
  | otherwise       = show n

問題をそのまま書けばOK

他の書き方

-- さっきの書き方
main = do print $ map plus3 [1, 5, 3]

plus3 n = n + 3
main = do print $ map (\n->n+3) [1, 5, 3]
-- 詳しくは googleで無名関数を検索
main = do print $ map (+ 3) [1, 5, 3]
-- 詳しくは googleで部分適用を検索

遅延評価

本日最後の問題です

ここにある正の整数xがあります。xは2桁以上です。この数字の並びが13進法表記であるとみなすと、10進法表記であると見なした場合の倍数になります。この条件を満たす最も小さいxを求めるプログラムを書いてください。 倍数になる13進数 - どう書く?org

解答例

main = print $ head $ filter(\n->(radix_13_10 n `mod` n)== 0)[10..]

radix_13_10 :: Int -> Int
radix_13_10 0 = 0
radix_13_10 n = (radix_13_10 (n `div` 10)) * 13 + (n `mod` 10)

radix_13_10は引数の数値を13進法表記と見なして、それを10進数にした値を返す関数です(読み飛ばしていただいて構いません)。

filterはリストから、条件を満たす要素のリストを返す関数です。

headはリストから、先頭の要素を返す関数です。

読んでみましょう

filter(\n->(radix_13_10 n `mod` n)== 0)[10..]

リスト:10以上の整数

条件:13進法表記と見なした場合の数値を10進数表記と見なした場合の数値で割った余りが0。つまり、10進法表記で見なした場合の倍数になっていること。

10以上の整数っていくつまで? これって、無限ループじゃない?

でも動きます

確かにそこだけ見ると無限リストを対象にしているので処理が終わりません。

もう少し読む範囲を拡げてみましょう。

head $ filter(\n->(radix_13_10 n `mod` n)== 0)[10..]

リスト、条件:さっきと同じ

その中の先頭の要素。

なので、実際使うのは一つだけです。実は、Haskellは必要な分だけ評価を行うので、[10..]を全部評価するのではなく、最初の一つが見つかるところまでしか評価を行いません。

なにそれ? 遅延評価です

もう一個欲しいな

最小の数だけじゃなくて、少ない方から2つ欲しいとなった場合。

take 2 $ filter(\n->(radix_13_10 n `mod` n)== 0)[10..]

リスト、条件:さっきと同じ

その中の最初の2個。

これでOK

もう一回解答例

main = print $ head $ filter(\n->(radix_13_10 n `mod` n)== 0)[10..]

radix_13_10 :: Int -> Int
radix_13_10 0 = 0
radix_13_10 n = (radix_13_10 (n `div` 10)) * 13 + (n `mod` 10)

こんな書き方もできます

main = print $ head $ [x | x <- [10..], radix_13_10 x `mod` x == 0]

radix_13_10 :: Int -> Int
radix_13_10 0 = 0
radix_13_10 n = (radix_13_10 (n `div` 10)) * 13 + (n `mod` 10)

まとめ

まとめ

→定義を書けば動く

参考資料

Haskellについて

参考資料

本資料は「S6」というプレゼンテーションツールを利用させていただいています。

以上です