Haskell   選択

ホーム   目次


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

if


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 をつけてもエラーにはなりません。

else

Haskell の if は必ず値を返さなければなりません。したがって、else 節を省略することはできません。次の例は、else 節で返す値が何もない場合の例です。

sign n =
    if n > 0
    then (Just n)
    else Nothing
    
*Main> sign 5
Just 5
*Main> sign 0
Nothing
*Main>

JustNothing はMaybeモナドいう型で使われるキーワードです。Just は値が一個あることを表し、Nothing は値が何もないことを表しています。Maybeモナドの内部は、値のない Nothing と、値のある Just という二つの要素を持つリストになっています。リストについては リスト で説明します。Just 5 を、普通の 5 に戻す方法は後ほど説明します。

(注意) if 式の中の then節 と else節 の返す値の型は同じでなければなりません。そのため、then の場合には、Just n を返して値の型を揃えています。

if のネスト

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

少し、頭が混乱してきそうですが、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では、ダイアログが返した値を切り分けるために、結構使います。

Just n から n を取り出す

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では、Nothings が表示されていますが、case 式の中では、return () の効果によって、何も表示されなくなっています。

print

ターミナルに :t 識別子 と入力すると識別子の引数の型と戻り値の型が表示されます。

Prelude> :t print
print :: Show a => a -> IO ()

一番右の型が、戻り値の型です。print 関数が ( ) (空の) IO モナドを返していることがわかります。 文字列が表示されるのは、戻り値を返した時の副作用として実現されています。


11594 visits
Posted: Jan. 05, 2019
Update: Jan. 21, 2019

ホーム   目次   ページトップ