Haskell   入出力

ホーム   目次


この章では、Haskellの入出力を説明しています。

入出力

Haskellは、同じ引数に対して、同じ戻り値を返すことが保証されている純粋関数型言語です。そして引数をもとにして計算を行って戻り値を返す事以外は何もしないことを、プログミング用語では参照透過性と言います。それに対して、関数の実行の結果、コンピュータの状態を変化させることを副作用と言います。具体的に言うと、ディスプレイの文字列を変更することも副作用になります。Haskellは参照透過性を守って副作用を許さない言語ですが、これでは、入出力が来ないことになります。

参照透過性と副作用については、一応、Wikipediaからの短い参照もあげておきます。

参照透過性

参照透過性(さんしょうとうかせい、英: Referential transparency)は、計算機言語の概念の一種である。 ある式が参照透過であるとは、その式をその式の値に置き換えてもプログラムの振る舞いが変わらない(言い換えれば、同じ入力に対して同じ作用と同じ出力とを持つプログラムになる)ことを言う。 Wikipedia参照

副作用

プログラミングにおける副作用(ふくさよう)とは、ある機能がコンピュータの(論理的な)状態を変化させ、それ以降で得られる結果に影響を与えることをいう。 代表的な例は変数への値の代入である。 例えば与えられた数字を二倍して返す機能" double "があるとする。 Wikipedia参照

Haskellでの対応策

そこでHaskellでは、「値」と「副作用を処理することができる機能」を戻り値にまとめることによって、この相反する事柄に対応することにしました。副作用を処理できる関数はモナドという型クラスのインスタンスです。型クラスとインスタンスについては タイプで説明しています。参照透過性の関数と区別するため、モナド型クラスのメソッド (関数) をアクションと呼びます。

IO アクション

putStrputStrLnprint、および標準入力から一行を取得する getLine は、IO ラムダというラムダ型のアクション (関数) です。

これの関数がどういう型の引数を取り、どういう型の戻り値を返すかは、ghci:t 関数名と入力すると分かります。

*Main> :t putStr
putStr :: String -> IO ()
*Main> :t putStrLn
putStrLn :: String -> IO ()
*Main> :t print
print :: Show a => a -> IO ()
*Main> :t getLine
getLine :: IO String

putStrputStrLnprint は、引数としてString型をとりますが、実際に返す値は、IO ()です。これは、IO ラムダ型の ( ) (ユニット、空の値)です。文字列の出力は IO ( )を返す時の副作用として処理されています。一方、getLineは、IO ラムダでラッピングされたString型を返します。このラムダでラッピングされた値は、何かの方法で取り出すか、ラムダでラッピングされた値を処理できる関数でしか処理できません。実際にgetLine++ "hello"というように、純粋なString型と結合することはできません。

直列化

複数のアクションが使われる場合は、どのような順番でアクションが実行されるかを決めて、一つのアクションにまとめなければなりません。このアクションの順番を決めて一つにまとめることを直列化と言います。直列化をする方法はいくつかあります。すでに使っている do もアクションの直列化を行っていますが、ここでは、一応他の方法も紹介しておきます。

>>= と >>

>>= という二項演算子のことを bind (バインド)と呼びます。bindの左側のアクションを実行して、その戻り値を引数として、右側のアクションを実行するという順番になります。左側のアクションの戻り値を使わない場合は >> と記述します。


greeting =
    putStr "enter your name: " >>
    getLine >>= \name ->
    putStrLn $ "Hello " ++ name ++ "!"
    
Prelude> greeting
enter your name: app
Hello app!

このように、>>=>>と使って複数のアクションをつないでいくことができます。

return

returnは、IO モナドだけでなく、すべてのモナドで使える関数です。returnは引数として受け取ったデータをモナド値にして返します。

次の例では入力した数字をそのまま印字します。数字以外を入力した場合は、エラーになります。


getInt =
    putStr "enter integer: " >>
    getLine >>= \line ->
    return (read line::Int)
    
Prelude> getInt
enter integer: 500
500

次の例ではno 1と入力した場合だけ文字列が表示されます。数字以外が入力された場合はエラーになります。


no 1 = putStrLn "You choosed no1."
no _ = return ()
    
Prelude> no 2
Prelude> no 1
You choosed no1.

do

doは、複数のアクションを、左から、もしくは上から順番に実行される、一つのアクションとしてまとめます。


names = do
    putStrLn "John"; putStrLn "Mary"
    putStrLn "Jimy"
    putStrLn "Jane"
    
Prelude> names
John
Mary
Jimy
Jane

<-

do ブロックの中でアクションから「値」を取り出すには、<-を使います。この時に、「副作用の処理」も実行されます。


greeting = do
    putStr "enter your name: "
    name <- getLine
    putStrLn $ "Hello " ++ name ++ "!"
    
Prelude> greeting
enter your name: app
Hello app!

let

do プロックの中で in を省略したletを記述すると、アモナド値ではない値を識別子に束縛できます。モナド値も束縛できますが、「値」を取り出して「副作用の処理」をするには、改めて<-を使わなければなりません。


greeting = do
    putStr "enter your name: "
    name <- getLine
    let h = "hello "
    putStrLn $ h ++ name ++ "!"
    

実行結果は先ほどの例と同じです。


11797 visits
Posted: Jan. 20, 2019
Update: Jan. 22, 2019

ホーム   目次   ページトップ