Haskell   反復

ホーム   目次


プログラミングは、選択(条件分岐)と反復(繰り返し)が使えれば、大体の処理ができるそうです。この章では、Haskellの反復について紹介します。Hakellの反復では再帰という方法が使われます。

なお、このページは、もともと「再帰とfor文」というタイトルでしたが、このコーナーを、個別のトピックスから、全体を網羅するチュートリアルに書き換えるために2019年1月17日に「反復」というタイトルに変更しました。

再帰関数

多くの命令型言語では、反復をさせるために for 文を使います。しかし、Haskellには for 文がないので、再帰関数を代わりに使う、と良く言われます。

再帰関数とは、関数内部で、自分自身の関数を呼び出す関数のことです。再帰関数の多くの例題では、factorial (階乗)が使われていますが、それでは少し難しいので、ここでは sum (合計)を求めることにします。

なお、sumは、組み込み関数として、すでに存在していますので、ここでは total という関数名にしました。

total.hs


total :: Int -> Int
total 0 = 0
total n = n + total(n-1)
    

total.hsを記述できましたら、ターミナルまたはコマンドプロンプトを起動して total.hsを保存したディレクトリに移動してください。

ghci

ターミナルもしくはコマンドプロンプトに ghci と入力してエンターキーを押すとHaskellのREPL (インタープリター)が起動します。

GHCi, version 8.0.2: https://www.haskell.org/ghc/ :? for help
Prelude>

Prelude>が表示されましたらghciは正常に起動しています。

:l total

total.hs を読み込みます。ghci のコマンドは : (コロン)ではじめる決まりになっています。 :l は、:load と記述しても構いません。読み込むファイルの拡張子 .hs は、つけなくても構いません。(当然、つけても構いません) ghci を終了するには、:q と、入力します。

Ok, modules loaded: Main.
*Main>

上記のように表示されれば、読み込みは成功しています。

total 10
55

total10 という引数を与えて、実行します。55 と表示されれば、正常に動作しています。

コード説明

total :: Int -> Int

total関数の型を定義しています。total関数は一個のInt(整数)を引数として受け取り、一個のInt(整数)を戻り値として返します。

total 0 = 0

totalに0が与えられた場合は0を返すという意味です。この部分h再帰関数の「基底部」と呼晴れます。基底部はそこで処理が終わること意味しています。

totale n = n + total(n-1)

基底部に達するまで、引数の値を一つずつ減らしながら、totalを呼び出し続けるという意味です。この部分を再帰関数の「再帰部」と呼びます。Haskellでは変数に値を再代入することはできません。ここでの「n」は「total」関数が呼び出されるたびに作成されて破棄されるローカル変数になるということらしいです。

結果として、ここでは 10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + 0 という処理が行われて 55 が返されることになります。

次は、同じサンプルをガードを使って記述した例です。


total :: Int -> Int
total n
    | n == 0    = 0
    | otherwise = n + total (n - 1)
    

再帰関数でfor文を作ってみる。

それでは再帰を使って、もっとfor文らしいものを作ってみます。for文の代表例としては、ある動作を指定した回数繰り返すことでしょう。

hellofor.hs


hellofor :: Int -> IO ()
hellofor 0 = return ()
hellofor n = do
    putStrLn "hello"
    hellofor(n-1)
    

これをghciに読み込んで、「hellofor 5」と実行すると「hello」が5回表示されます。

基底部である「hellofor 0 = return ()」で処理を終了しています。そのため hello は5回だけ表示されます。

*Main> hellofor 5
hello
hello
hello
hello
hello
*Main>

print で文字列を出力すると、出力された文字列が二重引用符 " で囲まれますが、putStrLn で文字列を出力すると二重引用符 " はつきません。

次に、for文でやりたいことは、現在の値を表示することだと思います。

countdownfor.hs


countdownfor :: Int -> IO ()
countdownfor 0 = return ()
countdownfor n = do
    print n
    countdownfor(n-1)
    
*Main> countdownfor 5
5
4
3
2
1
*Main>

普通、for文は現在の値をカウントアップしていきます。次のサンプルは現在の値をカウントアップするように書き換えてみました。

countupfor.hs


countupfor :: Int -> Int ->IO ()
countupfor n max
    | n > max = return ()
	| n == max = do
		print n
	| otherwise = do
		print n
		countupfor (n+1) max
    
*Main> countupfor 1 5
1
2
3
4
5
*Main>

基底部 (関数が抜け出すポイント) は、上記のように複数設けても構いません。

forM_

ここまでやってみて、やっぱり最初に関数を作っておかなければならないのは面倒だな、と思うことでしょう。実際のところ、Haskellにもfor文はあります。それが forM_ とい関数です。for文と言うより for-in や foreach に近いものです。

なお、Haskellはすべてが式なので、for文と言うよりも、for式という方が正しいでしょう。

ghci に直接、次のように入力してください。

*Main> import Control.Monad
*Main Control.Monad> forM_ [1..5] $ \i -> print i
1
2
3
4
5

コード説明

import Control.Monad

importは、ライブラリを読み込みます。ここではControl.Monadというライブラリを読み込んでいます。 Contrl.Monadの中に、forM_ 関数が定義されています。ファイルに記述する場合は、ファイルの先頭で、他のライブラリとともに改行で区切って import Control.Monad と記述します。他のライブラリとの読み込む順序は気にしてくても大丈夫です。

forM_ [1..5] $ \i -> print i

[1..5]は、リストと呼ばれるデータ型です。リストについては リストで説明します。\i -> print iは、 ラムダ式というものです。ラムダ式は、定義しなくても使える関数です。詳しくは、 ラムダで説明しています。


11800 visits
Posted: Dec. 21, 2018
Update: Jan. 21, 2019

ホーム   目次   ページトップ