クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

Haskellの再帰と遅延評価

このレッスンのあとで: 再帰の関数を書き、無限リストが使える理由を説明することができる。

75分のレッスンコマンド8件Haskell ラボ:関数と型で学ぶ関数型プログラミングをブラウザーで

終わりのない表から必要な分だけ

遅延評価とは、値が本当に必要になるまで、計算を先送りする仕組みです。そのため Haskell では、終わりのない無限リストを書いても、必要な分だけを取り出して使えます。

電車の時刻表で、「毎日あとに続く発車時刻の全部」を式で書いておき、次の3本だけを見る場面を考えます。全部を先に作らず、見る分だけ作るので、無限でも困りません。

このレッスンのゴール

メモ

再帰の関数を書き、無限リストが使える理由を説明することができる。

キーワード

ことばいみれい
再帰関数が自分自身を呼ぶこと
基底再帰を止める、最も単純な場合
遅延評価値が必要になるまで計算しないこと
無限リスト終わりのないリスト[1 ..]
takeリストの先頭から指定の個数だけ取る関数take 3 [1 ..]

学生が入力するセルは、このプレビューでは空白です。

再帰は基底と自己呼び出しでできる

Haskell には for 文がありません。繰り返しは、再帰と、第5回の map や fold で書きます。

再帰の関数は、2つの部分でできています。止まる場合を答える基底と、少し小さくした問題を自分に任せる部分です。リストなら、空のリストが基底です。

例: リストの長さを数える

コード: haskell
-- 例です。count は、リストの要素の数を返します。
count :: [Int] -> Int
-- 基底: 空のリストの要素は 0 個です。
count [] = 0
-- 再帰: 先頭の 1 個と、残りの個数を足します。
count (_ : rest) = 1 + count rest

例: count [5, 6, 7] の計算

手順式わかること
1count [5, 6, 7]1 + count [6, 7] になる
21 + count [6, 7]1 + (1 + count [7]) になる
31 + (1 + (1 + count []))基底で count [] は 0
41 + (1 + (1 + 0))答えは 3

学生が入力するセルは、このプレビューでは空白です。

再帰の関数を呼ぶ

コマンド
docker exec haskell ghc -e 'let count :: [Int] -> Int; count [] = 0; count (_ : rest) = 1 + count rest in count [5, 6, 7]'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

階乗を再帰で求める

コマンド
docker exec haskell ghc -e 'let fact :: Int -> Int; fact 0 = 1; fact n = n * fact (n - 1) in fact 5'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

遅延評価は必要になるまで計算しない

遅延評価では、式は、結果が必要になったときに初めて計算されます。使われない部分は、計算されません。

undefined は、計算すると必ずエラーになる値です。でも、使われなければ何も起きません。下の2つの式は、undefined を含んでも、エラーなしで答えを出します。

使わない部分は計算されない

コマンド
docker exec haskell ghc -e 'fst (3, undefined)'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

引数を使わない関数の場合

コマンド
docker exec haskell ghc -e 'const 1 undefined'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

無限リストは必要な分だけ使える

[1 ..] は、1から先へ終わりなく続く無限リストです。遅延評価なので、先頭の数個を取るだけなら、その分しか作られません。

take 5 [1 ..] は、先頭の5個を返します。takeWhile (< 40) は、条件に合う間だけ取り続けます。

無限リストの先頭を取る

コマンド
docker exec haskell ghc -e 'take 5 [1 ..]'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

40より小さい平方数だけ取る

コマンド
docker exec haskell ghc -e 'takeWhile (< 40) (map (^2) [1 ..])'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

100より大きい最初の3の倍数

コマンド
docker exec haskell ghc -e 'take 1 (filter (> 100) (map (*3) [1 ..]))'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

自分自身で作るフィボナッチ数

コマンド
docker exec haskell ghc -e 'let fibs = 0 : 1 : zipWith (+) fibs (drop 1 fibs) in take 10 fibs'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

よくある間違い

注意

「無限リストを書くと、メモリが尽きる」と考える人が多くいます。使った分しか作られないので、take などで有限にすれば安全です。ただし、length や sum で無限リストの全体を求める式は、終わりません。

Python のジェネレータで遅延を見る

コード: python
# yield は、値を1つ出して止まります。次に頼まれるまで動きません。
def naturals():
    n = 0
    while True:
        yield n
        n = n + 1


stream = naturals()
print(next(stream), next(stream), next(stream))
# print の中の next を 1 つ増やして、続きの値が出ることを確かめましょう。

このラボとソースについて: Haskell ラボ:関数と型で学ぶ関数型プログラミングをブラウザーで

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。