Difference between revisions of "Hakell I/Oアクションの紹介"
Ymotongpoo (talk | contribs) |
m (Assigned category based on output of automated translators...) |
||
(6 intermediate revisions by one other user not shown) | |||
Line 1: | Line 1: | ||
+ | Haskellでプログラミングするときに副作用があるような処理、あるいは外部に対して働きかけるような処理を書きたいときは '''アクション''' を使います。 |
||
− | When we're programming in Haskell and we want to do something that has a |
||
+ | アクションはHaskellの言語仕様の中では、3という数字や<hask>"hello world"</hask>という文字列、あるいは<hask>map</hask>という関数と同様に値として扱われます。 |
||
− | side effect, something that affects the world in some way, we use '''actions'''. |
||
+ | つまり変数名に束縛したり、関数に引数として与えたり、関数の結果とすることが可能ということです。 |
||
− | Actions are values in the Haskell language, much like the number three, the |
||
+ | Haskellが扱う他の値と同様に、アクションにも型があります。多くの種類のアクションがありますが、ここでは '''IO''' アクションと呼ばれる非常に重要なアクションから始めましょう。 |
||
− | string <hask>"hello world"</hask>, or the function <hask>map</hask>. They can be bound to variable |
||
+ | このアクションはプログラムの外部に対して働きかけることができるアクションです。ここにIOアクションの例を示します: |
||
− | names, passed into a function as an argument or be the result of a function. |
||
+ | * コンソールに "hello" という文字列を表示する |
||
− | Like all other Haskell values, every action has a type. There are many kinds |
||
+ | * コンソールから入力行を読み取る |
||
− | of actions but we'll start with a very important one called an '''IO''' action. |
||
+ | * www.google.comに対して80番ポートでネットワーク接続を確立する |
||
− | These are the actions that can change the world outside of the programming. |
||
+ | * ターミナルから2行入力を読み込んで、数字として処理し、足し算をして結果を表示する |
||
− | Here are some examples of IO actions: |
||
+ | * マウスの動きを入力として、スクリーンにグラフィックを表示するファーストパーソン・シューティングゲーム |
||
− | * Print the string "hello" to the console. |
||
− | * Read a line of input from the console. |
||
− | * Establish a network connection to www.google.com on port 80. |
||
− | * Read two lines of input from the terminal, interpret them as numbers, add them together and print out the result. |
||
− | * A first-person shooter game that uses mouse movements as input and renders graphics to the screen. |
||
+ | 以上をみてわかるように、IOアクションは非常に単純なこと(文字列を出力する)から非常に複雑なこと(テレビゲーム)まで多岐にわたります。 |
||
− | As you can see, IO actions range from the very simple (printing a string) to |
||
+ | またIOアクションはHaskellプログラムで使われる値として結果を残すことも可能であるということに気付いたと思います。 |
||
− | very complex (a video game). You may have also noticed that IO actions can also |
||
+ | コンソールから入力行を読み取る処理でポイントとなるのは、プログラムにデータを渡す部分です。 |
||
− | result in a value that can be used by the Haskell program. The point of reading |
||
+ | アクション型は値型のように結果として提示するもの(たとえばString)と同様にアクションの種類(IO)も反映します。 |
||
− | a line of input from the console is to provide data to the program. The type |
||
+ | たとえば、コンソールから入力行を読み取るというアクションは '''IO String''' という型を持っています。実際、すべてのIOアクションは '''a''' という結果の型に対して '''IO a''' という型を追っています。 |
||
− | of an action reflects the kind of action (IO) as well as the type of value that |
||
+ | アクションがプログラムにとくに結果を返さない場合は、結果を表すのにユニット型( '''()''' と表記されます)が使われます。 |
||
− | it provides as a result (for example String). We say that the action that |
||
+ | C, C++, Javaといったプログラミング言語を知っている人はユニット型が"void"型の返り値と似たものだと思って下さい。上で述べたIOアクションには次のような型があります: |
||
− | reads a line of input from the console has the type '''IO String'''. In |
||
− | fact, all IO actions will have a type '''IO a''' for some result type '''a'''. |
||
− | When an action doesn't provide any useful data back to the program the |
||
− | unit type (written '''()''') is used to denote the result. For programmers |
||
− | familiar with C, C++ or Java, this is similar to the return type of "void" |
||
− | in those languages. The IO actions mentioned above have the following types: |
||
− | * |
+ | * コンソールに "hello" という文字列を表示する: '''IO ()''' |
− | * |
+ | * コンソールから入力行を読み取る: '''IO String''' |
− | * |
+ | * www.google.comに対して80番ポートでネットワーク接続を確立する: '''IO Socket''' |
+ | * ターミナルから2行入力を読み込んで、数字として処理し、足し算をして結果を表示する: '''IO Int''' |
||
− | * Read two lines of input from the terminal, interpret them as numbers, add them together and print out the result: '''IO Int''' |
||
+ | * マウスの動きを入力として、スクリーンにグラフィックを表示するファーストパーソン・シューティングゲーム: '''IO ()''' |
||
− | * A first-person shooter game that uses mouse movements as input and renders graphics to the screen: '''IO ()''' |
||
+ | アクションがプログラムに使われる値を結果として返す一方で、引数には一切とりません。 '''putStrLn''' を考えてみましょう。 '''putStrLn''' は次のような型を持っています: |
||
− | While actions can result in values that are used by the program, they do |
||
− | not take any arguments. Consider '''putStrLn'''. It has the following |
||
− | type: |
||
<haskell> |
<haskell> |
||
putStrLn :: String -> IO () |
putStrLn :: String -> IO () |
||
</haskell> |
</haskell> |
||
+ | <hask>PutStrLn</hask> は引数をとりますが、アクションではありません。引数を1つ(文字列)とって、IO () というアクションの型を返す関数です。 |
||
− | <hask>PutStrLn</hask> takes an argument, but it is not an action. It is a function |
||
+ | そういう意味で、putStrLnはアクションではないですが、<hask>putStrLn "hello"</hask>はアクションです。微妙な違いでしかないですが、重要なことです。すべてのIOアクションはある型aに対するIO a型を持っています。IOアクションではそれ以上の引数を絶対にとりませんが、アクションを作る関数(たとえば<hask>putStrLn</hask>)は引数をとります。 |
||
− | that takes one argument (a string) and returns an action of type IO (). |
||
− | So putStrLn is not an action, but <hask>putStrLn "hello"</hask> is. The distinction is |
||
− | subtle but important. All IO actions are of type IO a for some type a. |
||
− | They will never require additional arguments, although a function which |
||
− | makes the action (such as <hask>putStrLn</hask>) may. |
||
+ | アクションというのは説明書のようなものです。アクションによって何ができるかを示しています。アクションそれ自身は何か処理を実行することはありませんが、 "実行される" ことによって何かが起きます。単にアクションがあるだけでは何も起きないのです。例えばHaskellでは<hask>putStrLn "hello"</hask>は"hello"という行を表示するアクションです。型としては<hask>IO ()</hask>を持っています。たとえば次のような定義をもつHaskellプログラムを書いてみます。 |
||
− | |||
− | Actions are like directions. They specify something that can be |
||
− | done. They are not active in and of themselves. They need |
||
− | to be "run" to make something happen. Simply having an action lying |
||
− | around doesn't make anything happen. For example, <hask>putStrLn "hello"</hask> is an action |
||
− | in haskell that prints the line "hello". It |
||
− | has type <hask>IO ()</hask>. We can write a Haskell program that contains the definition |
||
<haskell> |
<haskell> |
||
x = putStrLn "hello" |
x = putStrLn "hello" |
||
</haskell> |
</haskell> |
||
+ | しかしこれではプログラムに"hello"と表示させることはできないのです!Haskellは"main"と呼ばれるIOアクションしか実行できないことになっています。このアクションは<hask>IO ()</hask>という型を持っています。次のHaskellプログラムは"hello"を表示する"でしょう": |
||
− | but that doesn't cause the haskell program to print out "hello"! Haskell |
||
− | only runs one IO action in a program, the action called '''main'''. This |
||
− | action should have the type <hask>IO ()</hask>. The following haskell program |
||
− | ''will'' print out "hello": |
||
<haskell> |
<haskell> |
||
module Main where |
module Main where |
||
Line 67: | Line 43: | ||
</haskell> |
</haskell> |
||
+ | == 注意してください == |
||
− | == Do Notation == |
||
+ | 「じゃあ1つのIOアクションしか実効できないなら、どうやってHaskellプログラムで実用的な事をさせられるんだろう」と疑問に思うかもしれません。前のほうで述べたように、IOアクションというのは非常に複雑です。たくさんの簡単なアクションをつなげて、複雑なアクションを作ることができます。アクションをつなげるために '''doブロック''' を使います。 |
||
− | You may be wondering how any Haskell program can do anything useful if it |
||
− | can only run a single IO action. As we saw earlier, IO actions can be |
||
− | very complex. We can combine many simple actions together to form more |
||
− | complicated actions. To combine actions together we use a '''do-block'''. |
||
+ | doブロックは2つ以上のアクションを1つのアクションとしてつなげるものです。たとえば2つのIOアクションが結合されてその返り値がIOアクションだった場合、実行されるとまず最初のアクションが処理されて、その後に2番目のアクションが実行されます。簡単な例を見てみましょう。 |
||
− | A do-block combines together two or more actions into a single action. |
||
− | When two IO actions are combined the result is an IO action that, when |
||
− | invoked, performs the first action and then performs the second action. |
||
− | Here's a simple example. |
||
<haskell> |
<haskell> |
||
main :: IO () |
main :: IO () |
||
Line 83: | Line 53: | ||
putStrLn "world" |
putStrLn "world" |
||
</haskell> |
</haskell> |
||
+ | main は"hello"を表示して、その後 "world" を表示するアクションです。 |
||
− | Main is an action that prints a line "hello" and then prints a line |
||
+ | もし最初のアクションが副作用を持っていても、2つ目のアクション実行時にはその副作用を観測できる状態にあります。 |
||
− | "world". |
||
+ | たとえば、最初のアクションでファイルに書き込みが行われて、2番目のアクションで読み込みが行われたとき、ファイルの変更点は読み込みに反映されます。 |
||
− | If the first action had any side effects, those effects are visible |
||
+ | IOアクションはプログラムに結果を返すことができると言ったのを思い出してください。doブロックの結果はdoブロックの最後のアクションの結果となります。先ほどの例では、最後のアクション(<hask>putStrLn</hask>)は特に結果を生成しないため、doブロック全体の型は<hask>IO ()</hask>となります。 |
||
− | to the second action when it is performed. For example, if a file is |
||
− | written in the first action and read in the second action, the change |
||
− | to the file will be visible to the read. Remember that IO actions can |
||
− | return results to the program. The result of a do-block is the result |
||
− | of the last action in the do block. In our example above, the last |
||
− | action (<hask>putStrLn "world"</hask>) doesn't provide a useful result |
||
− | and so the type of the entire do-block is <hask>IO ()</hask>. |
||
+ | doブロックはあるアクションの結果を他のアクションの生成のために使うこともできます。たとえばこんな具合です: |
||
− | Do-blocks can also make use of the result of one action when constructing |
||
− | another action. For example: |
||
<haskell> |
<haskell> |
||
main:: IO () |
main:: IO () |
||
Line 102: | Line 65: | ||
putStrLn ("you said: " ++ line) |
putStrLn ("you said: " ++ line) |
||
</haskell> |
</haskell> |
||
+ | この例では<hask>getLine</hask>(<hask>getLine :: IO String</hask>)というアクションを使っています。このアクションはコンソールから入力行を読み取ります。doブロックは呼ばれたときに<hask>getLine<hask>を呼び出し、その結果を受け取って<hask>putStrLn ("you said: " ++ line)</hask>というアクションを先ほどの結果を<hask>line</hask>に束縛した形で呼び出します。 |
||
− | This example uses the action <hask>getLine</hask> (<hask>getLine :: IO String</hask>) which reads a line of input from the console. The do-block |
||
− | makes an action that, when invoked, invokes the <hask>getLine</hask>, |
||
− | takes its result and invokes |
||
− | the action <hask>putStrLn ("you said: " ++ line)</hask> with the previous |
||
− | result bound to <hask>line</hask>. |
||
+ | ここで等号ではなく、矢印(<hask><-</hask>)が束縛に用いられていることに注意してください。(<hask>let</hask>や<hask>where</hask>を使うときは等号を使います) |
||
− | Notice that |
||
+ | 矢印はアクションの結果がいままさに束縛されています、ということを表しています。<hask>getLine</hask>の型は<hask>IO String</hask>です。そして矢印はアクションの結果を<hask>line</hask>に束縛します。その型は<hask>String</hask>になります。 |
||
− | an arrow (<hask><-</hask>) is used in the binding and not an equal sign |
||
− | (as is done when binding with <hask>let</hask> or <hask>where</hask>). |
||
− | The arrow indicates that the result of an action is being bound. The |
||
− | type of <hask>getLine</hask> is <hask>IO String</hask>, and the arrow |
||
− | binds the result of the action to <hask>line</hask> which will be of |
||
− | type <hask>String</hask>. |
||
+ | これまでdoブロックを2つのアクションを繋げるために使ってきました。これによりもっと多くのアクションを繋ぐことも可能とわかります: |
||
− | We've used do-blocks to combine two actions together. This provides enough |
||
− | power to combine more actions together: |
||
<haskell> |
<haskell> |
||
main :: IO () |
main :: IO () |
||
Line 128: | Line 81: | ||
putStrLn ("you said: " ++ line1 ++ " and " ++ line2) |
putStrLn ("you said: " ++ line1 ++ " and " ++ line2) |
||
</haskell> |
</haskell> |
||
+ | 最下層のdoブロックはアクションなので、<hask>getLine</hask>と他のアクションを一緒に繋げることができます。そして、<hask>getLine</hask>は<hask>putStrLn "Enter two lines"</hask>と繋げることができ、より複雑なアクションを作ることができます。 |
||
− | Since the innermost do-block is an action, it can be combined with |
||
+ | 幸いなことに、こんな面倒な書き方をする必要はありません。doブロックでは複数のアクションを1つのブロックに書き下すことができます。 |
||
− | <hask>getLine</hask> to make another action, which can be combined with |
||
+ | マルチアクション・ブロックの意味は前述の入れ子の例をみれば明らかだと思います。束縛はそれ以降の全てのアクションから観測可能です。先の例はこのようにコンパクトに書き直すことができます。 |
||
− | <hask>putStrLn "Enter two lines"</hask> to make another more complicated |
||
− | action. Luckily we don't have to go through all this trouble. Do-blocks |
||
− | allow multiple actions to be specified in a single block. The meaning |
||
− | of these multi-action blocks is identical to the nested example above: the bindings are made visible to all successive actions. The previous |
||
− | example can be rewritten more compactly as |
||
<haskell> |
<haskell> |
||
main :: IO () |
main :: IO () |
||
Line 144: | Line 93: | ||
</haskell> |
</haskell> |
||
+ | もちろん、プログラムを書くときは他の書き方をしても一向に構いません。全てのアクションは<hask>main</hask>に書く代わりに、共通の操作を、別のアクションとしたり、アクションを生成する関数としたりして切り出したくなることもあるでしょう。たとえば、プロンプトの表示と、ユーザの入力をくっつけたくなった場合: |
||
− | Of course we are free to use other Haskell language features when writing |
||
− | our program. Instead of putting all of our actions in <hask>main</hask> |
||
− | we may want to factor some common operations out as separate actions or |
||
− | functions that build actions. For example, we may want to combine |
||
− | prompting and user input: |
||
<haskell> |
<haskell> |
||
promptLine :: String -> IO String |
promptLine :: String -> IO String |
||
Line 161: | Line 106: | ||
putStrLn ("you said: " ++ line1 ++ " and " ++ line2) |
putStrLn ("you said: " ++ line1 ++ " and " ++ line2) |
||
</haskell> |
</haskell> |
||
+ | ここでは<hask>promptLine</hask>という関数を作りました。この関数はアクションを返します。 |
||
− | Here we made a function <hask>promptLine</hask> which returns an action. |
||
+ | このアクションはプロンプトを表示して(<hask>putStr :: IO ()</hask>を使っています。これは文字列を改行せずに表示します)、コンソールから入力を読み込みます。 |
||
− | The action prints a prompt (using <hask>putStr :: IO ()</hask>, which prints a |
||
+ | このアクションの結果は最後のアクションの結果、つまり<hask>getLine</hask>となります。 |
||
− | string without a newline character) and reads a line from the console. |
||
− | The result of the action is the result of the last action, <hask>getLine</hask>. |
||
+ | 2行読みこんで、それらを結合して返すような便利な関数を書いてみましょう。 |
||
− | Let's try to write a slightly more helper function that reads two lines |
||
− | and returns both of them concatenated together: |
||
<haskell> |
<haskell> |
||
promptTwoLines :: String -> String -> IO String |
promptTwoLines :: String -> String -> IO String |
||
Line 175: | Line 118: | ||
line1 ++ " and " ++ line2 -- ?? |
line1 ++ " and " ++ line2 -- ?? |
||
</haskell> |
</haskell> |
||
+ | ここで問題発生です。これまでで、2行の入力行を読み込んでそれを結合する方法はわかっていますが、どうやって結合された文字列を返したらいいのか分かりません。doブロックはアクションをつなげて、その結果は最後のアクションの結果となることを思い出してください。 |
||
− | There's a problem here. We know how to prompt for and read in both |
||
+ | <hask>line1 ++ " and " ++ line2</hask>は文字列でありアクションではないので、doブロックの最後に使うことはできません。 |
||
− | lines of input, and we know how to combine those lines of input, but |
||
+ | いまここで必要なのは、ある特定の値を結果とするアクションを作る方法です。これこそまさに<hask>return</hask>関数が行う内容です。returnはどのような型の値も引数にとって、そのような値を結果とするアクションを作る関数です。 |
||
− | we don't have an action that results in the combined string. Remember, |
||
+ | これでヘルパー関数を完成させることができます: |
||
− | do-blocks combine together actions and the result of the do-block |
||
− | is the result of the last action. <hask>line1 ++ " and " ++ line2</hask> |
||
− | is a string, not an action resulting in a string and so cannot be |
||
− | used as the last line of the do-block. What we need is a way to |
||
− | make an action that results in a particular value. This is exactly |
||
− | what the <hask>return</hask> function does. Return is a function that |
||
− | takes any type of value and makes an action that results in that value. |
||
− | We can now complete our helper: |
||
<haskell> |
<haskell> |
||
promptTwoLines :: String -> String -> IO String |
promptTwoLines :: String -> String -> IO String |
||
Line 198: | Line 134: | ||
putStrLn ("you said " ++ both) |
putStrLn ("you said " ++ both) |
||
</haskell> |
</haskell> |
||
+ | この例では <hask>return (line1 ++ " and " ++ line2)</hask> はアクション型<hask>IO String</hask>であり、その型自身は外部世界に影響を与えることはないですが、これで<hask>line1</hask>と<hask>line2</hask>を結合した文字列を結果とすることができました。 |
||
− | In this example <hask>return (line1 ++ " and " ++ line2)</hask> is an |
||
− | action of type <hask>IO String</hask> that doesn't affect the outside world in any way, but results in a string that combines <hask>line1</hask> and <hask>line2</hask>. |
||
+ | ここで、多くの初心者が混乱してしまう、非常に重要な点を押さえておきましょう。それは"return" はプログラムの制御フローには営業をおよぼさ"ない"ということです! |
||
− | Here's a very important point that many beginners get confused |
||
+ | returnはdoブロックの実行を"中断しません"。returnはよくdoブロックの途中で使われますが、それが必ずしもそのdoブロックの結果に関係があるというわけでもありません。returnというのは単なる関数で、特定の値を結果とするアクションを生成するというだけのことです。言い換えれば、値をアクションに"ラップしている"ということです。 |
||
− | about: "return" does ''not'' affect the control flow of the program! |
||
− | Return does ''not'' break the execution of the do-block. Return may |
||
− | occasionally be used in the middle of a do-block where it doesn't |
||
− | directly contribute to the result of the do-block. |
||
− | Return is simply a function that makes |
||
− | an action whose result is a particular value. In a sense it |
||
− | ''wraps'' up a value into an action. |
||
+ | if-then-elseやcase-ofあるいはアクションの再帰のようなHaskellの制御フローを使うこともできます。たとえば: |
||
− | We can also use Haskell's control flow features such as if-then-else, |
||
− | case-of or recursion with actions. For example: |
||
<haskell> |
<haskell> |
||
main :: IO () |
main :: IO () |
||
Line 221: | Line 149: | ||
</haskell> |
</haskell> |
||
+ | <hask>if</hask>が真偽値によって2つの値から1つを選ぶような制御だということを思い出してください。 |
||
− | Recall that <hask>if</hask> chooses between two alternative values based on a |
||
+ | ここでは<hask>if</hask>を<hask>promptLine "What do you want?"というアクションの結果にもとづいて2つのアクションから洗濯するような制御として使いました。この<hask>if</hask>の結果はdoブロックがより大きなアクションへと取り込みます。もし複数のアクションを<hask>then</hask>節や<hask>else</hask>節で実行したいと思ったら、さらに別のdoブロックを使ってそれらを1つのアクションにまとめなければいけません。 |
||
− | boolean value. |
||
− | Here we used <hask>if</hask> to choose between two actions based on |
||
− | the result of the <hask>promptline "What do you want? "</hask> action. |
||
− | The result of the <hask>if</hask> is an action which the do-block |
||
− | combines into a larger action. If we want several actions to be performed |
||
− | within a <hask>then</hask> or <hask>else</hask> clause, we have to |
||
− | combine them into a single action using another do-block: |
||
<haskell> |
<haskell> |
||
main :: IO () |
main :: IO () |
||
Line 240: | Line 162: | ||
</haskell> |
</haskell> |
||
+ | let束縛もdoブロック内で用いることができます。このような形です: |
||
− | Let-bindings are also available within do-blocks, for example: |
||
<haskell> |
<haskell> |
||
main :: IO () |
main :: IO () |
||
Line 249: | Line 171: | ||
putStrLn "Bye." |
putStrLn "Bye." |
||
</haskell> |
</haskell> |
||
+ | doブロックを使っているときは、<hask>let</hask>を使うにあたってより便利な構文があります。<hask>in</hask>キーワードや入れ子を使わなくて良いというのがそれなのですが、この構文を使うときは、束縛された変数のスコープはdoブロック内の残りのアクションとなります。これは矢印を用いたdoブロック内でのdo束縛のスコープと同様です。先の例はより簡潔に書けます: |
||
− | When working in do-blocks there is a more convenient syntax for using |
||
− | <hask>let</hask> which does not require the <hask>in</hask> keyword or |
||
− | any nesting. When using this syntax, the scope of the bound variable |
||
− | includes the remainder of the do-block, just as the scope of do-bindings |
||
− | (using arrows) within the do-block. The previous example can be written |
||
− | more compactly as: |
||
<haskell> |
<haskell> |
||
main :: IO () |
main :: IO () |
||
Line 263: | Line 180: | ||
putStrLn "Bye." |
putStrLn "Bye." |
||
</haskell> |
</haskell> |
||
+ | let束縛とdo束縛はdoブロック内で自由に使うことができます。 |
||
− | Let-bindings and do-bindings can be freely interspersed within the do-block. |
||
+ | == 逃げられません == |
||
+ | 最後に一つ、ぜひ知っておくべきIOアクションの詳細について紹介します。 |
||
+ | それはアクションには逃げ道がない!ということです。つまりIOアクションから結果を得たければ、(<hask>main</hask>を通じて)IOアクションを呼び起こして、その結果を他のIOアクションを通じて外部に影響を与えるしか方法はないのです。IOアクションから結果だけを取り出して値を得ること(逆<hask>return</hask>)はできないのです。IOアクションの結果が"ラップされていない状態で"存在できるのはdoブロックの中だけなのです。 |
||
+ | こう言い切ってはしまいましたが、ここで告白することがあります。たぶんあとで気づくことになりますが、実はこれは一部間違いがあります。非常にまれな状況で、IOから脱出することができるのですが、その方法を使わない方が良い理由が多いのでここでは上のように結論づけました。 |
||
− | == There's no escape == |
||
− | There's one final detail about IO actions that you should be aware of: |
||
− | there is no escape! The only way to get a result from an IO action is |
||
− | to invoke the IO action (through <hask>main</hask>) and have its result |
||
− | used to affect the outside world through another IO action. There is |
||
− | no way to take an IO action and extract just its results to a simple |
||
− | value (an inverse-<hask>return</hask>). The only places where an IO |
||
− | action's results appear ''unwrapped'' are within a do-block. |
||
− | Having said that, I have a confession to make: you will probably |
||
− | find out later that this is not entirely true. There are some rare |
||
− | exceptions that can be used in extreme circumstances to escape from |
||
− | IO, but there are good reasons why you will not be using them. |
||
+ | == まとめ == |
||
+ | * IOアクションはプログラムの外の世界に影響を与えるために使われる。 |
||
+ | * アクションは引数はとらないが、結果の値は持つ。 |
||
+ | * アクションは実行されるまで不活性なまま。Haskellプログラムの中ではたった1つのIOアクション(<hask>main</hask>)だけが実行される。 |
||
+ | * doブロックは複数のアクションを1つのアクションにまとめる。 |
||
+ | * まとめられたIOアクションは副作用が観測できる形で順番に実行される。 |
||
+ | * 矢印はdoブロック内でアクションの結果を束縛するために使われる。 |
||
+ | * returnはアクションを作る関数である。制御フローの形式では"ない”。 |
||
+ | Languages: [[Introduction to Haskell IO/Actions|en]] |
||
+ | [[Category:Jp]] |
||
− | == Summary == |
||
− | * IO actions are used to affect the world outside of the program. |
||
− | * Actions take no arguments but have a result value. |
||
− | * Actions are inert until run. Only one IO action in a Haskell program is run (<hask>main</hask>). |
||
− | * Do-blocks combine multiple actions together into a single action. |
||
− | * Combined IO actions are executed sequentially with observable side-effects. |
||
− | * Arrows are used to bind action results in a do-block. |
||
− | * Return is a function that builds actions. It is ''not'' a form of control flow! |
||
− | |||
− | Languages: [[Introduction to Haskell IO/Actions|en]] |
Latest revision as of 03:11, 9 April 2021
Haskellでプログラミングするときに副作用があるような処理、あるいは外部に対して働きかけるような処理を書きたいときは アクション を使います。
アクションはHaskellの言語仕様の中では、3という数字や"hello world"
という文字列、あるいはmap
という関数と同様に値として扱われます。
つまり変数名に束縛したり、関数に引数として与えたり、関数の結果とすることが可能ということです。
Haskellが扱う他の値と同様に、アクションにも型があります。多くの種類のアクションがありますが、ここでは IO アクションと呼ばれる非常に重要なアクションから始めましょう。
このアクションはプログラムの外部に対して働きかけることができるアクションです。ここにIOアクションの例を示します:
- コンソールに "hello" という文字列を表示する
- コンソールから入力行を読み取る
- www.google.comに対して80番ポートでネットワーク接続を確立する
- ターミナルから2行入力を読み込んで、数字として処理し、足し算をして結果を表示する
- マウスの動きを入力として、スクリーンにグラフィックを表示するファーストパーソン・シューティングゲーム
以上をみてわかるように、IOアクションは非常に単純なこと(文字列を出力する)から非常に複雑なこと(テレビゲーム)まで多岐にわたります。 またIOアクションはHaskellプログラムで使われる値として結果を残すことも可能であるということに気付いたと思います。 コンソールから入力行を読み取る処理でポイントとなるのは、プログラムにデータを渡す部分です。 アクション型は値型のように結果として提示するもの(たとえばString)と同様にアクションの種類(IO)も反映します。 たとえば、コンソールから入力行を読み取るというアクションは IO String という型を持っています。実際、すべてのIOアクションは a という結果の型に対して IO a という型を追っています。 アクションがプログラムにとくに結果を返さない場合は、結果を表すのにユニット型( () と表記されます)が使われます。 C, C++, Javaといったプログラミング言語を知っている人はユニット型が"void"型の返り値と似たものだと思って下さい。上で述べたIOアクションには次のような型があります:
- コンソールに "hello" という文字列を表示する: IO ()
- コンソールから入力行を読み取る: IO String
- www.google.comに対して80番ポートでネットワーク接続を確立する: IO Socket
- ターミナルから2行入力を読み込んで、数字として処理し、足し算をして結果を表示する: IO Int
- マウスの動きを入力として、スクリーンにグラフィックを表示するファーストパーソン・シューティングゲーム: IO ()
アクションがプログラムに使われる値を結果として返す一方で、引数には一切とりません。 putStrLn を考えてみましょう。 putStrLn は次のような型を持っています:
putStrLn :: String -> IO ()
PutStrLn
は引数をとりますが、アクションではありません。引数を1つ(文字列)とって、IO () というアクションの型を返す関数です。
そういう意味で、putStrLnはアクションではないですが、putStrLn "hello"
はアクションです。微妙な違いでしかないですが、重要なことです。すべてのIOアクションはある型aに対するIO a型を持っています。IOアクションではそれ以上の引数を絶対にとりませんが、アクションを作る関数(たとえばputStrLn
)は引数をとります。
アクションというのは説明書のようなものです。アクションによって何ができるかを示しています。アクションそれ自身は何か処理を実行することはありませんが、 "実行される" ことによって何かが起きます。単にアクションがあるだけでは何も起きないのです。例えばHaskellではputStrLn "hello"
は"hello"という行を表示するアクションです。型としてはIO ()
を持っています。たとえば次のような定義をもつHaskellプログラムを書いてみます。
x = putStrLn "hello"
しかしこれではプログラムに"hello"と表示させることはできないのです!Haskellは"main"と呼ばれるIOアクションしか実行できないことになっています。このアクションはIO ()
という型を持っています。次のHaskellプログラムは"hello"を表示する"でしょう":
module Main where
main :: IO ()
main = putStrLn "hello"
注意してください
「じゃあ1つのIOアクションしか実効できないなら、どうやってHaskellプログラムで実用的な事をさせられるんだろう」と疑問に思うかもしれません。前のほうで述べたように、IOアクションというのは非常に複雑です。たくさんの簡単なアクションをつなげて、複雑なアクションを作ることができます。アクションをつなげるために doブロック を使います。
doブロックは2つ以上のアクションを1つのアクションとしてつなげるものです。たとえば2つのIOアクションが結合されてその返り値がIOアクションだった場合、実行されるとまず最初のアクションが処理されて、その後に2番目のアクションが実行されます。簡単な例を見てみましょう。
main :: IO ()
main = do
putStrLn "hello"
putStrLn "world"
main は"hello"を表示して、その後 "world" を表示するアクションです。
もし最初のアクションが副作用を持っていても、2つ目のアクション実行時にはその副作用を観測できる状態にあります。
たとえば、最初のアクションでファイルに書き込みが行われて、2番目のアクションで読み込みが行われたとき、ファイルの変更点は読み込みに反映されます。
IOアクションはプログラムに結果を返すことができると言ったのを思い出してください。doブロックの結果はdoブロックの最後のアクションの結果となります。先ほどの例では、最後のアクション(putStrLn
)は特に結果を生成しないため、doブロック全体の型はIO ()
となります。
doブロックはあるアクションの結果を他のアクションの生成のために使うこともできます。たとえばこんな具合です:
main:: IO ()
main = do
line <- getLine -- line :: String
putStrLn ("you said: " ++ line)
この例ではgetLine
(getLine :: IO String
)というアクションを使っています。このアクションはコンソールから入力行を読み取ります。doブロックは呼ばれたときにgetLine<hask>を呼び出し、その結果を受け取って<hask>putStrLn ("you said: " ++ line)
というアクションを先ほどの結果をline
に束縛した形で呼び出します。
ここで等号ではなく、矢印(<-
)が束縛に用いられていることに注意してください。(let
やwhere
を使うときは等号を使います)
矢印はアクションの結果がいままさに束縛されています、ということを表しています。getLine
の型はIO String
です。そして矢印はアクションの結果をline
に束縛します。その型はString
になります。
これまでdoブロックを2つのアクションを繋げるために使ってきました。これによりもっと多くのアクションを繋ぐことも可能とわかります:
main :: IO ()
main = do
putStrLn "Enter two lines"
do
line1 <- getLine -- line1 :: String
do
line2 <- getLine -- line2 :: String
putStrLn ("you said: " ++ line1 ++ " and " ++ line2)
最下層のdoブロックはアクションなので、getLine
と他のアクションを一緒に繋げることができます。そして、getLine
はputStrLn "Enter two lines"
と繋げることができ、より複雑なアクションを作ることができます。
幸いなことに、こんな面倒な書き方をする必要はありません。doブロックでは複数のアクションを1つのブロックに書き下すことができます。
マルチアクション・ブロックの意味は前述の入れ子の例をみれば明らかだと思います。束縛はそれ以降の全てのアクションから観測可能です。先の例はこのようにコンパクトに書き直すことができます。
main :: IO ()
main = do
putStrLn "Enter two lines"
line1 <- getLine -- line1 :: String
line2 <- getLine -- line2 :: String
putStrLn ("you said: " ++ line1 ++ " and " ++ line2)
もちろん、プログラムを書くときは他の書き方をしても一向に構いません。全てのアクションはmain
に書く代わりに、共通の操作を、別のアクションとしたり、アクションを生成する関数としたりして切り出したくなることもあるでしょう。たとえば、プロンプトの表示と、ユーザの入力をくっつけたくなった場合:
promptLine :: String -> IO String
pormptLine prompt = do
putStr prompt
getLine
main :: IO ()
main = do
line1 <- promptLine "Enter a line: " -- line1 :: String
line2 <- promptLine "And another: " -- line2 :: String
putStrLn ("you said: " ++ line1 ++ " and " ++ line2)
ここではpromptLine
という関数を作りました。この関数はアクションを返します。
このアクションはプロンプトを表示して(putStr :: IO ()
を使っています。これは文字列を改行せずに表示します)、コンソールから入力を読み込みます。
このアクションの結果は最後のアクションの結果、つまりgetLine
となります。
2行読みこんで、それらを結合して返すような便利な関数を書いてみましょう。
promptTwoLines :: String -> String -> IO String
promptTwoLines prompt1 prompt2 = do
line1 <- promptLine prompt1 -- line1 :: String
line2 <- promptLine prompt2 -- line2 :: String
line1 ++ " and " ++ line2 -- ??
ここで問題発生です。これまでで、2行の入力行を読み込んでそれを結合する方法はわかっていますが、どうやって結合された文字列を返したらいいのか分かりません。doブロックはアクションをつなげて、その結果は最後のアクションの結果となることを思い出してください。
line1 ++ " and " ++ line2
は文字列でありアクションではないので、doブロックの最後に使うことはできません。
いまここで必要なのは、ある特定の値を結果とするアクションを作る方法です。これこそまさにreturn
関数が行う内容です。returnはどのような型の値も引数にとって、そのような値を結果とするアクションを作る関数です。
これでヘルパー関数を完成させることができます:
promptTwoLines :: String -> String -> IO String
promptTwoLines prompt1 prompt2 = do
line1 <- promptLine prompt1 -- line1 :: String
line2 <- promptLine prompt2 -- line2 :: String
return (line1 ++ " and " ++ line2)
main :: IO ()
main = do
both <- promptTwoLines "First line: " "Second line: "
putStrLn ("you said " ++ both)
この例では return (line1 ++ " and " ++ line2)
はアクション型IO String
であり、その型自身は外部世界に影響を与えることはないですが、これでline1
とline2
を結合した文字列を結果とすることができました。
ここで、多くの初心者が混乱してしまう、非常に重要な点を押さえておきましょう。それは"return" はプログラムの制御フローには営業をおよぼさ"ない"ということです! returnはdoブロックの実行を"中断しません"。returnはよくdoブロックの途中で使われますが、それが必ずしもそのdoブロックの結果に関係があるというわけでもありません。returnというのは単なる関数で、特定の値を結果とするアクションを生成するというだけのことです。言い換えれば、値をアクションに"ラップしている"ということです。
if-then-elseやcase-ofあるいはアクションの再帰のようなHaskellの制御フローを使うこともできます。たとえば:
main :: IO ()
main = do
line <- promptLine "What do you want? " -- line :: String
if line == "wisdom"
then putStrLn "No man is without enemies."
else putStrLn ("I don't have any " ++ line)
if
が真偽値によって2つの値から1つを選ぶような制御だということを思い出してください。
ここではif
をpromptLine "What do you want?"というアクションの結果にもとづいて2つのアクションから洗濯するような制御として使いました。この<hask>if
の結果はdoブロックがより大きなアクションへと取り込みます。もし複数のアクションをthen
節やelse
節で実行したいと思ったら、さらに別のdoブロックを使ってそれらを1つのアクションにまとめなければいけません。
main :: IO ()
main = do
line <- promptLine "What do you want? " -- line :: String
if line == "wisdom"
then putStrLn "No man is without enemies."
else do
putStrLn ("I don't have any " ++ line)
putStrLn "Perhaps you want some wisdom?"
let束縛もdoブロック内で用いることができます。このような形です:
main :: IO ()
main = do
line <- promptLine "Enter a value: " -- line :: String
let line2 = "\"" ++ line ++ "\"" in do -- line2 :: String
putStrLn ("you said " ++ line2)
putStrLn "Bye."
doブロックを使っているときは、let
を使うにあたってより便利な構文があります。in
キーワードや入れ子を使わなくて良いというのがそれなのですが、この構文を使うときは、束縛された変数のスコープはdoブロック内の残りのアクションとなります。これは矢印を用いたdoブロック内でのdo束縛のスコープと同様です。先の例はより簡潔に書けます:
main :: IO ()
main = do
line <- promptLine "Enter a value: " -- line :: String
let line2 = "\"" ++ line ++ "\"" -- line2 :: String
putStrLn ("you said " ++ line2)
putStrLn "Bye."
let束縛とdo束縛はdoブロック内で自由に使うことができます。
逃げられません
最後に一つ、ぜひ知っておくべきIOアクションの詳細について紹介します。
それはアクションには逃げ道がない!ということです。つまりIOアクションから結果を得たければ、(main
を通じて)IOアクションを呼び起こして、その結果を他のIOアクションを通じて外部に影響を与えるしか方法はないのです。IOアクションから結果だけを取り出して値を得ること(逆return
)はできないのです。IOアクションの結果が"ラップされていない状態で"存在できるのはdoブロックの中だけなのです。
こう言い切ってはしまいましたが、ここで告白することがあります。たぶんあとで気づくことになりますが、実はこれは一部間違いがあります。非常にまれな状況で、IOから脱出することができるのですが、その方法を使わない方が良い理由が多いのでここでは上のように結論づけました。
まとめ
- IOアクションはプログラムの外の世界に影響を与えるために使われる。
- アクションは引数はとらないが、結果の値は持つ。
- アクションは実行されるまで不活性なまま。Haskellプログラムの中ではたった1つのIOアクション(
main
)だけが実行される。 - doブロックは複数のアクションを1つのアクションにまとめる。
- まとめられたIOアクションは副作用が観測できる形で順番に実行される。
- 矢印はdoブロック内でアクションの結果を束縛するために使われる。
- returnはアクションを作る関数である。制御フローの形式では"ない”。
Languages: en