Skip to content

Commit 4a6c802

Browse files
committed
Complate the explanation of <*>
1 parent 770b29f commit 4a6c802

File tree

1 file changed

+35
-53
lines changed

1 file changed

+35
-53
lines changed

assets/20.md

+35-53
Original file line numberDiff line numberDiff line change
@@ -249,18 +249,16 @@ ghci> :t (<*>) @IO
249249
- 第2引数(右辺)の`IO a`: 「第1引数の命令が返す関数」の引数と同じ型`a`を返す命令。
250250
- 戻り値の`IO b`: 「第1引数の命令が返す関数」の戻り値と同じ型`b`を返す命令。
251251

252-
第1引数の`IO (a -> b)`がかなり変わっている`a -> b`を返す命令なんて一体どこから出てくるのか
252+
第1引数の`IO (a -> b)`がかなり変わっていますね`a -> b`を返す命令なんて一体どこから出てくるのでしょうか?その答えは、先ほどの`(++) <$> getLine <*> getLine`という式における`<*>`の左辺、`(++) <$> getLine`にあります:
253253

254-
その答えは、先ほどの`(++) <$> getLine <*> getLine`という式における`<*>`の左辺、`(++) <$> getLine`にあります。
255-
256-
```
254+
```haskell
257255
ghci> :t (++) <$> getLine
258256
(++) <$> getLine :: IO ([Char] -> [Char])
259257
```
260258

261259
`IO ([Char] -> [Char])`、すなわち「『文字列を受け取って文字列を返す関数』を返す命令」が出てきました!
262260

263-
以下のように型が推論された結果です。
261+
以下のように`<$>`の型変数`a``b``f`が推論された結果です:
264262

265263
```haskell
266264
(++) <$> getLine
@@ -277,25 +275,20 @@ ghci> :t (++) <$> getLine
277275
f b
278276
```
279277

280-
`++`のような2つ以上の引数を受け取る関数を`<$>`に渡した場合、必ずこのような型が現れます。
281-
Haskellでは2つ以上の引数を受け取る関数は`a -> b -> c`(カッコを補うと「`a -> (b -> c)`」)という型の、「引数を1つ受け取ると、『残りの引数を受け取る関数』を返す関数」で表現されるので、`<$>`を使った結果に「関数を返す命令」が現れるのは、ある意味当然なことなのです。
278+
`++`のような2つ以上の引数を受け取る関数を`<$>`に渡した場合、必ずこのように`IO`の中に関数を含んだ型が現れます。Haskellでは2つ以上の引数を受け取る関数は`a -> b -> c`(カッコを補うと「`a -> (b -> c)`」)という型の、「引数を1つ受け取ると、『残りの引数を受け取る関数』を返す関数」で表現されるので、`<$>`を使った結果に「関数を返す命令」が現れるのは、当然なことなのです。
282279

283-
こうした場合に対応するのが`<*>`の役目です。
284-
改めて型宣言を思い出してみましょう。
280+
こうした場合に対応するのが`<*>`の役目です。改めて型宣言を思い出してみましょう:
285281

286-
```
282+
```haskell
287283
ghci> :t (<*>) @IO
288284
(<*>) @IO :: IO (a -> b) -> IO a -> IO b
289285
```
290286

291-
2つ以上の引数を受け取る関数を`<$>`に渡した結果現れる、`IO (a -> b)`のような型の値を左辺に受け取ることがはっきりと書かれていますね。
292-
そして、右辺で受け取っているのは、また命令です。「『第1引数の命令が返す関数』の引数と同じ型`a`を返す命令」であることから、左辺に渡された命令が返す関数に渡されることが予期されます。
293-
結果、最終的に`<*>`が返すのは、やっぱり命令です。「『第1引数の命令が返す関数』の戻り値と同じ型`b`を返す命令」とあるとおり、やはり左辺に渡された命令が返す関数の結果をそのまま返す命令なんでしょう。
287+
2つ以上の引数を受け取る関数を`<$>`に渡した結果現れる、`IO (a -> b)`型の値を左辺に受け取ることがはっきりと書かれていますね。そして右辺で受け取っているのは、また命令です。「『第1引数の命令が返す関数』の引数と同じ型`a`を返す命令」であることから、第1引数に渡された命令が返す関数に渡されることが予期されます。結果、最終的に`<*>`が返すのは、やっぱり命令です。「『第1引数の命令が返す関数』の戻り値と同じ型`b`を返す命令」とあるとおり、やはり第1引数に渡された命令が返す関数の結果をそのまま返す命令なんでしょう。
294288

295-
ここで出てきた`IO a`から結果となる値`a`を取り出して、`IO (a -> b)`の結果となる`a -> b`に渡すには、`IO (a -> b)``IO a`、両方を実行する必要があります。
296-
下記のような関数を書いて、実際に`<*>`が左辺の`IO (a -> b)`と右辺の`IO a`の両方を実行していることを確かめてみましょう。
289+
ここで出てきた`IO a`から結果となる`a`型の値を取り出して、`IO (a -> b)`の結果となる`a -> b`に渡すには、`IO (a -> b)``IO a`、両方を実行する必要があります。下記のような関数を書いて、実際に`<*>`が左辺の`IO (a -> b)`と右辺の`IO a`の両方を実行していることを確かめてみましょう:
297290

298-
```
291+
```haskell
299292
-- 関数を返す前に`putStrLn`を実行する命令
300293
returnDoubler :: IO (Integer -> Integer)
301294
returnDoubler = do
@@ -309,83 +302,72 @@ return4 = do
309302
return 4
310303
```
311304

312-
```
305+
```haskell
313306
ghci> returnDoubler <*> return4
314307
Returning a function
315308
Returning 4
316309
8
317310
```
318311

319-
`returnDoubler`に書いた`putStrLn`の後に、`return4`に書いた`putStrLn`が実行されましたね。
320-
そう、`<*>`は、左辺に渡した「関数を返す命令」を実行した後に、右辺に渡した「値を返す命令」を順番に実行しているのです。
321-
322-
以上から、`<*>`は、`<$>`が返した「関数を返す`IO`(命令)」と、`<*>`の右辺に渡したもう一つの`IO`(命令)を**続けて実行する**ための演算子であることがわかります。
312+
`returnDoubler`に書いた`putStrLn`の後に、`return4`に書いた`putStrLn`が実行されましたね。そう、`<*>`は、左辺に渡した「関数を返す命令」と、右辺に渡した「値を返す命令」を順番に実行しているのです。
323313

324-
`<$>`は、あくまでも**1つの**命令に対して結果を関数に渡す役割である一方`<*>`は、**2つ以上の**命令を、続けて実行して関数に渡す、ということを覚えておきましょう
314+
以上から、`<*>`は、`<$>`が返した「関数を返す`IO`(命令)」と、`<*>`の右辺に渡したもう一つの`IO`(命令)を**続けて実行する**ための演算子であることがわかります。`<$>`は、あくまでも**1つの**命令の結果を関数に渡す役割である一方`<*>`は、**2つ以上の**命令を続けて実行して関数に渡す、という点を覚えておいてください
325315

326-
「2つ以上」と書いたとおり、当然3つ以上の引数を受け取る関数に対しても`<*>`は使えます。
327-
例えば、今回の課題で定義しておくと便利であろう、「元金と金利(単位はパーセント)と年数を受け取って、年数後の元金を返す」関数を定義して、それに対して`<*>`を使ってみます。
316+
「2つ以上」と書いたとおり、もちろん3つ以上の引数を受け取る関数に対しても`<*>`は使えます。例えば、今回の課題で定義しておくと便利であろう、「元金と金利(単位はパーセント)と年数を受け取って、年数後の元金を返す」関数を定義して、それに対して`<*>`を使ってみます。
328317

329-
```
318+
```haskell
330319
-- Ref: https://support.microsoft.com/ja-jp/help/141695/xl-how-to-calculate-compound-interest
331320
yearlyRate :: Double -> Double -> Integer -> Double
332321
yearlyRate principal interestRate years =
333322
principal * (1 + interestRate / 100) ^ years
334323
```
335324

336-
試してましょう。
325+
試してましょう。`IO`型の値を用意するのに先程の`returnDoubler`のような関数を都度定義するのも面倒なので、`TypeApplications`を使って手っ取り早く`return @IO``IO`型の値を作ります[^return]:
337326

338-
```
339-
ghci> :set -XTypeApplications
327+
[^return]: 単なる`return`だと、ほかの`Monad`型クラスのインスタンスである型の`return`が採用される恐れがあります。`TypeApplications`を使って`return @IO`と書けば、「この`return`は必ず`IO`に対する`return`だよ」と明記できるのでこの問題を回避できるのです。
328+
329+
```haskell
340330
ghci> yearlyRate <$> return @IO 100.0 <*> return @IO 5.0 <*> return @IO 2
341331
110.25
342332
```
343333

344-
できました!
345-
3つめの引数を渡すときも`<*>`を使って残りの命令を渡すだけです。これは4つめ以降の引数でも変わりません。
346-
途中の式がどのように型付けされているかは自分で確かめてみてください。
334+
できました!
347335

348-
ちなみに、「適当な`IO`型の値を用意したい、でもいちいち考えるのが面倒くさい」と言うときは、上記のように、言語拡張`TypeApplications`を有効にした上で`return @IO`を使うと簡単です。
349-
(単なる`return`だと、ほかの`Monad`型クラスのインスタンスである型の`return`が採用される恐れがあります。`TypeApplications`を使って`return @IO`と書けば、「この`return`は必ず`IO`に対する`return`だよ」と明記できるのでこの問題を回避できるのです)
336+
3つめの引数を渡すときも、`<*>`を使って残りの命令を渡すだけです。4つめ以降の引数でも変わりません。途中の式がどのように型付けされているかは、自分で確かめてみてください。
350337

351-
それから、上記の例は下記のように、関数を定義しないで直接各種演算子に対して`<$>``<*>`を使うことによっても実現できます。
352-
ただ、ご覧の通り前置記法で二項演算子を何重も書くのはさすがにつらいので、この例ではやめておいた方がいいでしょう。
338+
それから、上記の例は下記のように、関数を定義しないで直接それぞれの演算子に対して`<$>``<*>`を使うというやり方でも実現できます。ただ、ご覧の通り前置記法で二項演算子を何重も書くのはさすがにつらいので、普段はやめておいた方がいいでしょう:
353339

354-
```
355-
ghci> (*) <$> return 100.0 <*> ((^) <$> ((1 +) <$> ((/ 100) <$> return 5.0)) <*> return 2)
340+
```haskell
341+
ghci> (*) <$> return @IO 100.0 <*> ((^) <$> ((1 +) <$> ((/ 100) <$> return @IO 5.0)) <*> return @IO 2)
356342
```
357343

358-
あるいは、ラムダ抽象を使うのも一つの手です
344+
あるいは、ラムダ抽象を使うのも一つの手です(相変わらず一行で書くには読みづらいでしょうが):
359345

360-
```
361-
ghci> (\principal interestRate years -> principal * (1 + interestRate / 100.0) ^ years) <$> return 100 <*> return 5.0 <*> return 2
346+
```haskell
347+
ghci> (\principal interestRate years -> principal * (1 + interestRate / 100.0) ^ years) <$> return @IO 100 <*> return @IO 5.0 <*> return @IO 2
362348
110.25
363349
```
364350

365-
もっと冗長だけどわかりやすいやり方、すなわち`do`記法を使ったやり方に立ち返って、
351+
もっと冗長だけどわかりやすいやり方、すなわち`do`記法を使ったやり方に立ち返ってもよいでしょう:
366352

367-
```
353+
```haskell
368354
ghci> :{
369355
ghci| do
370-
ghci| principal <- return 100.0
371-
ghci| interestRate <- return 5.0
372-
ghci| years <- return 2
373-
ghci| return $ principal * (1 + interestRate / 100) ^ years
356+
ghci| principal <- return @IO 100.0
357+
ghci| interestRate <- return @IO 5.0
358+
ghci| years <- return @IO 2
359+
ghci| return @IO $ principal * (1 + interestRate / 100) ^ years
374360
ghci| :}
375361
110.25
376362
```
377363

378-
と書いても、できあがる「命令」の処理内容は全く変わりません。
379-
380-
これらの`do``<$>``<*>`などとの使い分けは、ソースコードの見た目が変わる以外の違いに、ほとんど関係がありません(`IO`以外の型の扱いやGHCの`Strict`という言語拡張を使った場合など、いろいろ例外はありますが割愛します)。
381-
適宜読みやすいと思う書き方を選んでください。個人的には、迷ったらより冗長な方にするのをおすすめします。
364+
これらの`do``<$>``<*>`などを使い分けることは、ソースコードの見た目を変える以外にほとんど意味がありません(`IO`型以外の場合や、GHCの`Strict`という言語拡張を使った場合など、いろいろ例外はありますが割愛します)。上記のように`do`を使って書いても、できあがる「命令」の処理内容は変わらないのです。「`<$>``<*>`で簡潔に書けるけど、簡潔すぎて読みづらいな」と感じたら迷わず`do`記法を使って書き直してください。
382365

383366
#### `<$>`はなぜ引数が2つ以上の場合には使えないのか
384367

385-
「後述する」と言って積み残した課題がありました。
386-
試しに`<$>`を引数が2つ以上の関数に対して使ってみて、型エラーになることを確認してみましょう。
368+
「後述します」と言って積み残した課題がありました。試しに`<$>`を引数が2つ以上の関数に対して使ってみて、型エラーになることを確認してみましょう:
387369

388-
```
370+
```haskell
389371
ghci> (++) <$> getLine <$> getLine
390372

391373
<interactive>:16:1: error:

0 commit comments

Comments
 (0)