Skip to content

Commit

Permalink
Fill to the bottom of ex19
Browse files Browse the repository at this point in the history
  • Loading branch information
igrep committed Nov 10, 2024
1 parent 4a6c802 commit 563e6f2
Showing 1 changed file with 32 additions and 40 deletions.
72 changes: 32 additions & 40 deletions assets/20.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ Returning 4

`returnDoubler`に書いた`putStrLn`の後に、`return4`に書いた`putStrLn`が実行されましたね。そう、`<*>`は、左辺に渡した「関数を返す命令」と、右辺に渡した「値を返す命令」を順番に実行しているのです。

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

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

Expand Down Expand Up @@ -341,7 +341,7 @@ ghci> yearlyRate <$> return @IO 100.0 <*> return @IO 5.0 <*> return @IO 2
ghci> (*) <$> return @IO 100.0 <*> ((^) <$> ((1 +) <$> ((/ 100) <$> return @IO 5.0)) <*> return @IO 2)
```

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

```haskell
ghci> (\principal interestRate years -> principal * (1 + interestRate / 100.0) ^ years) <$> return @IO 100 <*> return @IO 5.0 <*> return @IO 2
Expand All @@ -365,7 +365,7 @@ ghci| :}

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

「後述します」と言って積み残した課題がありました。試しに`<$>`を引数が2つ以上の関数に対して使ってみて、型エラーになることを確認してみましょう:
「後述します」と言って積み残した課題がありました。簡単に言うと、前節で`<*>`を解説する際触れたとおり、`a -> b -> c`のような型の、2つ以上の引数を受け取る関数を`<$>`に渡すと`IO (b -> c)`というような型の「(残りの引数を受け取る)関数を返す命令」が出来てしまい、それ以上`<$>`では引数を渡せなくなってしまう、という理由です。ここでは実際にそれを確かめるため、`<$>`を引数が2つ以上の関数に対し使ってみて、どんな型エラーになるか見てみましょう:

```haskell
ghci> (++) <$> getLine <$> getLine
Expand All @@ -381,93 +381,85 @@ ghci> (++) <$> getLine <$> getLine
it :: IO b (bound at <interactive>:16:1)
```

`In the first argument of (<$>), namely (++) <$> getLine`という行に注目してください
`<$>`を2回使っているのでわかりづらいですがここでは2つめの`<$>`についての型エラーが報告されています
左辺に渡した式`(++) <$> getLine`の型が間違っているのです
`In the first argument of (<$>), namely (++) <$> getLine`という行に注目してください`<$>`を2回使っているのでわかりづらいですがここでは2つめの`<$>`についての型エラーが報告されています左辺に渡した式`(++) <$> getLine`の型が間違っているのです

具体的には左辺の型について

- 期待している型: `String -> b`すなわち文字列を受け取ってなにかのまだ決まっていない`b`の値を返す普通の関数」、に対して
- 実際に渡された式の型: `IO ([Char] -> [Char])`「『文字列を受け取って文字列を返す関数を返す命令となっている
- 実際に渡された式の型: `IO ([Char] -> [Char])`「『文字列を受け取って文字列を返す関数を返す**命令**となっている

というエラーです

このような型になった理由は`(++) <$> getLine`という式の型が`IO ([Char] -> [Char])`となった理由について説明したとおりです
`<$>`はあくまでも1つの命令を操作するための演算子であって`<*>`のように`IO ([Char] -> [Char])`と右辺が返す`IO [Char]`という2つの命令をつなげることができないのです
このような型になった原因は`(++) <$> getLine`という式の型が`IO ([Char] -> [Char])`となった理由について説明したとおりです繰り返しになりますが`<$>`はあくまでも1つの命令を操作するための演算子であって`<*>`のように`IO ([Char] -> [Char])`と右辺が返す`IO [Char]`という2つの命令をつなげることができないのです

#### `>>=`との違い

課題19で学習した`>>=`はいずれも`<*>`と同様2つの`IO`命令**続けて実行**する`IO`命令を作るための演算子です
課題hoge(19?)で学習した`>>=`はいずれも`<*>`と同様2つの`IO`命令**続けて実行**する`IO`命令を作るための演算子です

課題19より
課題hoge(19?)でも確かにそう紹介しています:

> 左辺の命令の実行結果を受け取って右辺の命令を返す関数につなげて**続けて実行**する命令を返す演算子
> まとめると`>>=`は左辺の命令の実行結果を受け取って右辺の命令を返す関数につなげて続けて実行する命令を返す演算子と言えます

そうした意味においていずれも用途は似ているのですがそれぞれ使用できる場面が異なります
ここではその違いについて紹介します
そうした意味においていずれも用途は似ているのですがそれぞれ使用できる場面が異なりますここではその違いを解説します

一言で言うと、「`<*>`は`<$>`と同様にあくまでも**純粋な関数**を`IO`命令の結果に対して適用するためのものであることに対して`>>=`は**`IO`命令を返す関数**を別の`IO`命令の結果に対して適用するものであるという点です
そこに大きな隔たりがあります
一言で言うと、「`<*>`は`<$>`と同様にあくまでも**純粋な関数**を`IO`命令の結果に対して適用するためのものであることに対して`>>=`は**`IO`命令を返す関数**を別の`IO`命令の結果に対して適用するものであるという点が異なりますそこに大きな隔たりがあります

なぜ`putStrLn <$> getLine`と書いても`putStrLn`は実行されないのかを説明した節で挙げた例を思い出してください

```
```haskell
putStrLn =<< getLine
```

これを`<$>`や`<*>`で書き換えようとして
上記の「`getLine`でユーザーから入力してもらった文字列を`putStrLn`で出力する」という命令を`<$>``<*>`でも作ってみようとして、

```
```haskell
putStrLn <$> getLine
```

と書いても、`IO ()`ではなく`IO (IO ())`が返されてしまい、その内側の`IO`にあたる`getLine`を実行するだけにとどまってしまう、という問題があるのでした。
...と書いても、`IO ()`ではなく`IO (IO ())`が返されてしまい、その内側の`IO`にあたる`getLine`を実行するだけにとどまってしまう、という問題があるのでした。

同じことが`<*>`でも起こります。
2つの文字列を受け取って、それらを結合して`putStrLn`する命令を返す関数を考えてみましょう。
同じことが`<*>`でも起こります。2つの文字列を受け取って、それらを結合して`putStrLn`する命令を返す関数を例にします:

```
putTwoLns :: String -> String -> IO ()
putTwoLns s1 s2 = putStrLn $ s1 ++ s2
```haskell
putTwoLines :: String -> String -> IO ()
putTwoLines s1 s2 = putStrLn $ s1 ++ s2
```

これを、`<$>`と`<*>`を組み合わせて、`getLine`を2回実行した結果に対して使用してみます
これを、`<$>``<*>`を組み合わせて、`getLine`を2回実行した結果に対して使用してみます:

```
ghci> putTwoLns <$> getLine <*> getLine
```haskell
ghci> putTwoLines <$> getLine <*> getLine
123 -- ここはユーザーによる入力
456 -- ここはユーザーによる入力
ghci>
```

`putStrLn <$> getLine`と書いたときと同様に、`<$>`と`<*>`、それぞれの右辺に渡した`getLine`を実行することはできたのですが、`putTwoLns`は実行されませんでした。
やはりこれは、`putStrLn <$> getLine`を実行したときと同じ現象です。
`<$>`も`<*>`もどちらも、`IO (IO ())`を`IO ()`に変えることができないのです。
`putStrLn <$> getLine`と書いたときと同様に、`<$>``<*>`、それぞれの右辺に渡した`getLine`を実行することはできたのですが、`putTwoLines`を実行することはできませんでした。やはりこれは、`putStrLn <$> getLine`を実行したときと同じ現象です。`<$>``<*>`もどちらも、`IO (IO ())``IO ()`に変えることができないのです。

今回のようなケースを`>>=`で修正する場合、次のように書けば良いでしょう。
(もちろん`do`を使って書いた方が読みやすいケースですが、今回は割愛します。やってみてください!)
今回のようなケースを`>>=`で修正する場合、次のように書けば良いでしょう:

```
ghci> getLine >>= (\s1 -> getLine >>= (\s2 -> putTwoLns s1 s2))
```haskell
-- もちろん`do`を使って書いた方が読みやすいケースですが、今回は割愛します。やってみてください!
ghci> getLine >>= (\s1 -> getLine >>= (\s2 -> putTwoLines s1 s2))
123 -- ここはユーザーによる入力
456 -- ここはユーザーによる入力
123456
ghci>
```

あるいは、いっそのこと`putTwoLns`における文字列を結合する部分、すなわち`++`する部分を抜き出して、
あるいは、いっそのこと`putTwoLines`における文字列を結合する部分、すなわち`++`する部分を抜き出して、

```
```haskell
ghci> putStrLn =<< (++) <$> getLine <*> getLine
123 -- ここはユーザーによる入力
456 -- ここはユーザーによる入力
123456
ghci>
```

と書いてもいいでしょう。これならワンライナーで書けますね
と書いてもいいでしょう。これならワンライナーでもまあまあ読みやすいですね

話を戻しますが、どちらにしても、`getLine`を2回実行した結果を`putStrLn`に渡す際には`=<<`を使用している点に注意してください。
やっぱり命令の結果をまた別の命令を返す関数に渡すには、`=<<`が必須なのです。
話が逸れましたが、どちらにしても`getLine`を2回実行した結果を`putStrLn`に渡す際には、`=<<`を使用している点に注意してください。やっぱり命令の結果をまた別の命令を返す関数に渡すには、`=<<`が必須なのです。

### まとめ: `<$>`, `<*>`, `>>=`の使い分け

Expand Down

0 comments on commit 563e6f2

Please sign in to comment.