プログラミングは、選択(条件分岐)と反復(繰り返し)が使えれば、大体の処理ができるそうです。この章では、Haskellの選択について紹介します。Hakellでは選択に使えるいろいろな方法があります。
sign n =
if n > 0
then "plus"
else "minus"
上記のコードをソースファイルに保存して、ghci に読み込んでください。「sign 数字」と入力するとプラスかマイナスかを答えてくれます。
*Main> sign 0.2
"plus"
*Main> sign (-0.1)
"minus"
*Main>
マイナスの数字を入力する場合は、Haskellの決まりによって「sign (-0.1)」のように括弧を付けなければなりません。( )をつけなければ実行時エラーになります。
sign 関数に do
は付いていません。sign 関数にアクションが含まれていないからです。
なお、do
をつけてもエラーにはなりません。
if
は必ず値を返さなければなりません。したがって、else
節を省略することはできません。次の例は、else
節で返す値が何もない場合の例です。
sign n =
if n > 0
then (Just n)
else Nothing
*Main> sign 5
Just 5
*Main> sign 0
Nothing
*Main>
Just
と Nothing
はMaybe
モナドいう型で使われるキーワードです。Just
は値が一個あることを表し、Nothing
は値が何もないことを表しています。Maybe
モナドの内部は、値のない Nothing
と、値のある Just
という二つの要素を持つリストになっています。リストについては
リスト
で説明します。Just 5
を、普通の 5
に戻す方法は後ほど説明します。
(注意) if
式の中の then
節 と else
節 の返す値の型は同じでなければなりません。そのため、then
の場合には、Just n
を返して値の型を揃えています。
if
をネストさせて 0
が与えれた場合にも対応してみました。
sign n =
if n > 0
then "plus"
else if n < 0
then "minus"
else "zero"
*Main> sign 5
"plus"
*Main> sign (-5)
"minus"
*Main> sign 0
"zero"
*Main>
ガードは、1つの関数に、引数の条件を、複数定義できる機能です。if - else
のネストよりもコードが読みやすくなります。
sign n
| n > 0 = "plus"
| n < 0 = "minus"
| otherwise = "zero"
ガードに do
を書くとすれば =
の後ろになりますが、この例では記述しても意味はありません また、if
の =
の後にも do
を書くことができますが、前述の例では書いても意味はありません。
otherwise は、すべての条件に当てはまるというキーワードです。ガードは if - else のネストと同じように上から順番に評価されます。otherwiseを最初に書くとすべての値がotherwiseに当てはまってしまいます。
次は、成績を求める例です。
grade n
| n < 0 || n > 100 = "It is a number outside of range"
| n < 60 = "It is failing"
| n < 69 = "The Grade is C"
| n < 79 = "The Grade is B"
| otherwise = "The grade is A"
*Main> grade 120
"It is a number outside of range"
*Main> grade 100
"The grade is A"
*Main> grade 80
"The grade is A"
*Main> grade 60
"The Grade is C"
*Main> grade 40
"It is failing"
*Main> grade (-20)
"It is a number outside of range"
順序が逆になりましたが、Haskellの選択の基本は、このパターンマッチらしいです。
ifやガードは、一つの関数で、色々な引数の値に対応しようとしましたが、パターンマッチは、引数の値ごとに同じ関数を複数定義するという方法です。
ここでは、パターンマッチを使って、number 関数を定義してみました。この例では浮動小数点数が入力された場合、othereになりますが、サンプルとして掲載しておきます。
number 0 = "zero"
number 1 = "one"
number 2 = "two"
number 3 = "three"
number n = "other"
*Main> number 0
"zero"
*Main> number 1
"one"
*Main> number 2
"two"
*Main> number 2.5
"other"
次の例では、0未満と4以上を検知するために、ガードと組み合わせています。
number 0 = "zero"
number 1 = "one"
number 2 = "two"
number 3 = "three"
number n
| n < 0="minus"
| otherwise = "many"
*Main> number 4
"many"
*Main> number (-1)
"minus"
0.1などのプラスの浮動小数点数を入力すると many になってしまいますが、あくまでもサンプルとしてみてください。なおマイナスの数値を入力する場合は (-1) と括弧をつけてください。
少し、頭が混乱してきそうですが、case of
は、一つの関数の中で、パターンマッチを行うような方法です。
sign n =
case n < 0 of
True -> "minus"
_ -> "plus"
パターンマッチでは、引数と値を結びつけるのに、 = を使いましたが、case ofでは、-> を使います。
_ (アンダースコア)は、値を無視するという意味になります。ここに otherwiseを使うことはできますが、ガードの条件に _ (アンダースコア)を使うことはできません。
0の場合にも対応するためにガードも組み合わせてみました。
sign n =
case n < 0 of
True -> "minus"
_ | n > 0 -> "plus"
| otherwise -> "zero"
次はパターンマッチで使った number 関数を case of で書き換えてみたサンプルです。引数として浮動小数点数を与えた場合は、「Other」が呼び出されます。
number n =
case n of
0 -> "zero"
1 -> "one"
2 -> "two"
3 -> "three"
_ -> "Other"
ここでも、ガードを使って0未満と4以上を切り分けてみます。
number n =
case n of
0 -> "zero"
1 -> "one"
2 -> "two"
3 -> "three"
_ | n < 0 -> "minus"
| otherwise -> "many"
case ofは、あまり使い道がないとも言われていますが、wxHaskell
では、ダイアログが返した値を切り分けるために、結構使います。
Maybeモナドを使った場合の、Just n
から n
を取り出すサンプルです。
wxHaskell
のダイアログは、ユーザーの選択結果を、Maybeモナド型として返します。そのMaybeモナドから値を取り出すために、このコード例は良く使われます。
sign n =
if n > 0
then (Just n)
else Nothing
main = do
let n = sign 50
print n
case n of
Nothing -> return ()
Just n -> print n
*Main> main
Just 50
50
*Main>
Nothing
の場合に、return ()
を返しています。return
は、IOモナドを作成するアクションです。引数の ()
は、値が何もないことを表すユニットと呼ばれるものです。同様に、print
も IOモナドを返すアクションです。このようにするとcase
でどちらが選ばれた場合でも、返す値の型が揃います。
次の例は sign 関数にマイナスの値を与えた場合の例です。
sign n =
if n > 0
then (Just n)
else Nothing
main = do
let n = sign (-50)
print n
case n of
Nothing -> return ()
Just n -> print n
*Main> main
Nothing
*Main>
一度目の pirnt n
では、Nothing
s が表示されていますが、case
式の中では、return ()
の効果によって、何も表示されなくなっています。
ターミナルに :t
識別子 と入力すると識別子の引数の型と戻り値の型が表示されます。
Prelude> :t print
print :: Show a => a -> IO ()
一番右の型が、戻り値の型です。print 関数が ( ) (空の) IO モナドを返していることがわかります。 文字列が表示されるのは、戻り値を返した時の副作用として実現されています。