Skip to content

Commit 770b29f

Browse files
committed
Complete the explanation of <$>, begin explaining <*>
1 parent 260b38e commit 770b29f

File tree

1 file changed

+57
-38
lines changed

1 file changed

+57
-38
lines changed

assets/20.md

+57-38
Original file line numberDiff line numberDiff line change
@@ -116,52 +116,73 @@ putStrLn <$> getLine :: IO (IO ())
116116

117117
以上の通り、「関数オブジェクト」とよく似たこの`IO`は単なる値として扱えるので、Haskellにおける関数`a -> b`の結果`b`として返したり、`putStrLn <$> getLine`が返す`IO (IO ())`のように、`IO`を実行した結果として返すことができます。
118118

119-
では肝心な点、その結果として返された`IO`はどうやって実行することができるのでしょうか?簡潔に言うと、`main`関数やGHCiの中で評価することで(`>>=``>>`でつなげられてその部分を通ったとき)初めて実行できます。ほかのプログラミング言語における「関数オブジェクト」と異なり、実行するための演算子が明確でなく、わかりづらくなっています
119+
では肝心な点、その結果として返された`IO`はどうやって実行することができるのでしょうか?簡潔に言うと、`main`関数やGHCiの中で評価することで(`>>=``>>`、あるいは`do`でつなげられて、その部分を通ったとき)初めて実行できます。ほかのプログラミング言語における「関数オブジェクト」と異なり、実行する専用の演算子[^execute-operator]はありません。ちょっと分かりづらいですね
120120

121-
先程の`putStrLn <$> getLine`を、今度こそ全部実行してみましょう:
121+
[^execute-operator]: JavaScriptやPythonの関数オブジェクトでは`()`、Rubyの`Proc`クラスのインスタンスやJavaの`Callable`を実装したオブジェクトでは`call`メソッドがその「実行する専用の演算子」に該当します。
122+
123+
例として、先程の`putStrLn <$> getLine`を今度こそ全部実行してみましょう:
122124

123125
```haskell
124-
ghci> :t putStrLn <$> getLine
125-
putStrLn <$> getLine :: IO (IO ())
126-
ghci> returnedAction <- putStrLn <$> getLine
127-
12345 -- ここはユーザーによる入力
128-
ghci> :t returnedAction
129-
returnedAction :: IO ()
130-
ghci> returnedAction
131-
12345
126+
ghci> action1 = putStrLn <$> getLine
127+
ghci> action1 -- 復習: この時点では putStrLn は実行されない
128+
aaaaa -- ここはユーザーによる入力
129+
ghci>
130+
ghci> :t action1 -- IO (IO ()) という型なので、内側の IO () が実行されるまで、putStrLn は実行されない
131+
action1 :: IO (IO ())
132+
133+
-- do の中で<- を使って action1 の中の IO () を取り出して、実行する
134+
ghci> :{
135+
ghci| do
136+
ghci| action2 <- action1
137+
ghci| action2
138+
ghci| :}
139+
aaaaa -- ここはユーザーによる入力
140+
aaaaa -- 入力した文字列が出力された!
132141
```
133142

134-
GHCiは、`putStrLn <$> getLine`という式を受け取ったとき、とりあず`IO (IO ())`の外側の`IO (...)`、つまりこの場合`getLine`に当たる「命令」を実行します。
135-
そして、その結果として`putStrLn <getLineが返した文字列>`という`IO ()`型の値(命令)を返しているのです。
136-
そのため、上記のように`putStrLn <$> getLine`から`<-`で(あるいは、`>>=`で)取り出した`IO ()`(上記で言うところの`returnedAction`)を直接GHCiに入力すれば`putStrLn <getLineが返した文字列>`の部分も実行することができます
143+
GHCiは、`putStrLn <$> getLine`という式を受け取ったとき、とりあず`IO (IO ())`の外側の`IO (...)`、つまりこの場合`getLine`に当たる「命令」を実行します。そして、その結果として`putStrLn <getLineが返した文字列>`という`IO ()`型の値(命令)を返しているのです。
144+
145+
そのため、上記のように`putStrLn <$> getLine`から`<-`で(あるいは、`>>=`で)取り出した`IO ()`(上記で言うところの`action2`)を直接`do`の中に含めてGHCiに入力することで初めて`putStrLn <getLineが返した文字列>`の部分も実行することができるようになるのです
137146

138147
`putStrLn <$> getLine`と書いても`putStrLn`の部分が実行されなかったのは、`getLine`が結果として返した`[Char]``putStrLn``IO ()`に変換した後、実行していなかったからなのです。
139148

140-
この、`putStrLn <$> getLine`と入力しても`putStrLn`が実行されない、という現象は、GHCiだけでなく、`main`関数に`putStrLn <$> getLine`と書いてしまった場合も同様に発生します。
149+
先程の`action2`を最終的に実行する`do`の型を`:t`を使って見てみると、ちゃんと入れ子じゃない、普通の`IO ()`になっていることが確認できます:
141150

151+
```haskell
152+
ghci> :{
153+
ghci| :t do
154+
ghci| action2 <- action1
155+
ghci| action2
156+
ghci| :}
157+
do
158+
action2 <- action1
159+
action2 :: IO ()
142160
```
161+
162+
なおこの`putStrLn <$> getLine`と入力しても`putStrLn`が実行されないという現象はGHCiだけでなく`main`関数に`putStrLn <$> getLine`と書いてしまった場合も同様に発生します
163+
164+
```haskell
143165
main = putStrLn <$> getLine
144166
```
145167

146168
というファイルを`not-echoed.hs`という名前で保存して実行してみても、やはりGHCiで実行した場合と同様に、`getLine`に当たる部分しか実行されません。
147169

148-
```
170+
```haskell
149171
shell> stack exec runhaskell not-echoed.hs
150172
12345 # ここはユーザーによる入力
151173
shell>
152174
```
153175

154-
しかし、多くのHaskellerが行うように、この問題は`main`関数に型注釈をつけていれば、型エラーとして回避できます。
155-
`not-echoed.hs`を次のように書き換えてみましょう。
176+
しかし、多くのHaskellerが行うように、この問題は`main`関数に型注釈をつけていれば、型エラーとして回避できます。`not-echoed.hs`を次のように書き換えてみましょう。
156177

157-
```
178+
```haskell
158179
main :: IO ()
159180
main = putStrLn <$> getLine
160181
```
161182

162-
今度は以下のような型エラーが報告されるので、問題のある`putStrLn <$> getLine`は実行されるまでもありません
183+
今度は以下のような型エラーが報告されるので、問題のある`putStrLn <$> getLine`は実行される間でもありません
163184

164-
```
185+
```haskell
165186
shell> stack exec runhaskell not-echoed.hs
166187
not-echoed.hs:2:8: error:
167188
? Couldn't match type IO () with ()
@@ -174,58 +195,56 @@ not-echoed.hs:2:8: error:
174195
| ^^^^^^^^^^^^^^^^^^^^
175196
```
176197

177-
`main`関数のように、`let``where`を伴わないで定義された、同じモジュール内のどの関数からも参照できる関数を、「トップレベルに定義された関数」と言います。
178-
`main`関数だけでなく、ほかのトップレベルの`IO`型の値を返す関数(あるいは`IO`型の値そのもの)にも型注釈を付けていれば、この問題は大抵回避できます。
179-
一般に、**トップレベルに定義する関数は、型注釈を書く**ことでインターフェースを明確にすることが望ましいとされています。
180-
なので、トップレベルの関数に型注釈を書くことは、この問題を回避する以外のメリットもあるので、なるべく書きましょう。
181-
当入門ではこれまで`main`の型注釈については省略してきましたが、今後は`main`についても記載します。
198+
`main`関数のように、`let``where`を伴わないで定義された、同じモジュール内のどの関数からも参照できる関数を、「トップレベルに定義された関数」と言います。`main`関数だけでなく、ほかのトップレベルの`IO`型の値を返す関数(あるいは`IO`型の値そのもの)にも型注釈を付けていれば、この問題は大抵回避できます。一般に、**トップレベルに定義する関数は、型注釈を書く**ことでインターフェースを明確にすることが望ましいとされています。なので、トップレベルの関数に型注釈を書くことは、この問題を回避する以外のメリットもあるので、なるべく書きましょう。当入門ではこれまで`main`の型注釈については省略してきましたが、今後は`main`についても記載します。
182199

183200
### `<*>`を使って2つ目以降の引数を「命令」の結果から渡す
184201

185-
`+`などの二項演算子といった、2つ以上の引数を受け取る関数の引数を`IO`などの「命令」の結果から渡す場合`<$>`だけではできません(理由は後述します)。
202+
`+`などの二項演算子といった、2つ以上の引数を受け取る関数の引数として`IO`などの「命令」の結果を渡す場合`<$>`だけではできません(理由は後述します)。
186203

187-
これまで下記のように`do`を使って書いていたものは、`<$>``<*>`を使って書き換えることができます。
204+
例えばこれまで、下記のように`do`を使って書いていた場合を例としましょう:
188205

189-
```
206+
```haskell
190207
-- `getLine`を2回実行して、取得した2つの文字列を ++ で結合する
191208
do
192209
line1 <- getLine
193210
line2 <- getLine
194211
return $ line1 ++ line2
195212
```
196213

197-
```
214+
このように二つ(以上)の「命令」が返した結果を、命令でない、純粋な関数に渡すコードは、`<$>``<*>`を使って次のように書き換えることができます:
215+
216+
```haskell
198217
(++) <$> getLine <*> getLine
199218
```
200219

201-
すっきり!やっぱりワンライナーで書けましたね!
202-
(課題18で学習した「演算子を前置関数に変換する」方法を思い出してください!)
220+
すっきり!やっぱりワンライナーで書けましたね!(課題18で学習した「演算子を前置関数に変換する」方法を思い出してください!)
203221

204-
実行例
222+
試しに実行すると、確かに`getLine`を2回実行して、その結果を`++`で結合していることがわかります:
205223

206-
```
224+
```haskell
207225
ghci> (++) <$> getLine <*> getLine
208226
first half -- ここはユーザーによる入力
209227
second half -- ここはユーザーによる入力
210228
"first halfsecond half"
211229
```
212230

213-
例のごとく、新しい関数を見つけたら型を見る、の精神で、`<*>`の型を覗いてみましょう
231+
例のごとく、新しい関数を見つけたら型を見る、の精神で、`<*>`の型を覗いてみましょう:
214232

215-
```
233+
```haskell
216234
ghci> :t (<*>)
217235
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
218236
```
219237

220-
`Monad`, `Functor`と続いて、`Applicative`というまた見知らぬ型クラスが登場しました。
221-
これらの詳細は次の課題で行いますので、今はまた`TypeApplications``f``IO`に置き換えましょう。
238+
`Monad`, `Functor`と続いて`Applicative`というまた見知らぬ型クラスが登場しましたこれらの詳細は次の課題で行いますので今はまた`TypeApplications`で`f`を`IO`に置き換えましょう
222239

223-
```
240+
```haskell
224241
ghci> :set -XTypeApplications
225242
ghci> :t (<*>) @IO
226243
(<*>) @IO :: IO (a -> b) -> IO a -> IO b
227244
```
228245

246+
`TypeApplications``f``IO`に絞っても、まだまだ複雑な型ですね。日本語で説明すると、`<*>`は両辺に次のような値を受け取り、次のような値を返す演算子となっています:
247+
229248
- 第1引数(左辺)の`IO (a -> b)`: 「普通の(入出力を行わない)関数 `a -> b`」を返す命令。
230249
- 第2引数(右辺)の`IO a`: 「第1引数の命令が返す関数」の引数と同じ型`a`を返す命令。
231250
- 戻り値の`IO b`: 「第1引数の命令が返す関数」の戻り値と同じ型`b`を返す命令。

0 commit comments

Comments
 (0)