Skip to content

Commit 260b38e

Browse files
committed
Introduction of <$>, and its caveat
1 parent 2e4fc61 commit 260b38e

File tree

1 file changed

+31
-44
lines changed

1 file changed

+31
-44
lines changed

assets/20.md

+31-44
Original file line numberDiff line numberDiff line change
@@ -8,60 +8,59 @@
88

99
従来、`IO`(命令)の結果に対して(`a -> IO b`というような型ではなく、`a -> b`という型の)普通の関数を実行するには、次のように`do`の中で左細い矢印`<-`を使って、一旦変数に代入する必要がありました。
1010

11-
```
11+
```haskell
1212
do
1313
line <- getLine
1414

1515
-- line 変数に代入して、length関数を実行してから何かする
1616
print (length line)
17+
1718
-- あるいは、length関数を実行してから何もせずにそのまま返す
1819
return (length line)
1920
```
2021

21-
課題19で紹介した`>>=`を使うことによって、`do`や代入する変数を用意する必要はなくなりましたが、`return`などの関数を使って、必ず何らかの形で`IO`(命令)に変換する必要がありました。
22+
課題hoge(19?)で紹介した`>>=`を使うことによって、`do`や代入する変数を用意する必要はなくなりましたが、`return`などの関数を使い、必ず何らかの形で右辺の関数を、`IO`(命令)を返す関数に変換する必要がありました:
2223

23-
```
24+
```haskell
2425
getLine >>= return . length
2526
```
2627

27-
この変換を不要にするのが`fmap`、あるいはその演算子バージョンである`<$>`の役目です(実際のところ`<$>`の方がよく使われるので、ここからは`<$>`で説明します)。
28-
上記の例を書き換えてみると、こう👇なります。
28+
この変換を不要にするのが`fmap`、あるいはその演算子バージョンである`<$>`の役目です(実際のところ`<$>`の方がよく使われるので、ここからは`<$>`で説明します)。
2929

30-
```
30+
上記の例を`<$>`を使って書き換えてみると、こう👇なります:
31+
32+
```haskell
3133
length <$> getLine
3234
```
3335

34-
順番が変わってしまっている点には、ご注意ください。
35-
`>>= return .`という部分が`<$>`に置き換わって、すっきりしましたね!
36+
`>==`から両辺に渡す式の型が入れ替わっている点には、ご注意ください。`<$>`では、左辺に「普通の関数」、右辺に「命令」を渡します。`>>= return .`という部分が`<$>`に置き換わって、すっきりしましたね!
3637

37-
このように`<$>`は、**`IO`(命令)を実行した結果に対して、普通の、純粋な関数を実行する**命令を作るための演算子です
38+
このように`<$>`は、**`IO`(命令)の結果**に対して、普通の、**純粋な関数**を実行する命令を作るための演算子です
3839

3940
そのことを裏付けるために、例のごとく`:t`で型定義を覗いてみましょう。
4041

41-
```
42+
```haskell
4243
ghci> :t (<$>)
4344
(<$>) :: Functor f => (a -> b) -> f a -> f b
4445
```
4546

46-
おっと、`Monad`でもない、`Functor`という聞き慣れない型クラスの型変数が出てきました。
47-
`Functor`の説明は一旦置いておいて、前の課題と同様`TypeApplications`を使って型変数`f``IO`に置き換えて解釈しましょう。
47+
おっと`Monad`でもない`Functor`という聞き慣れない型クラスの型変数が出てきました`Functor`の説明は一旦置いておいて前の課題と同様`TypeApplications`を使って型変数`f`を`IO`に置き換えて解釈しましょう
4848

49-
```
49+
```haskell
5050
ghci> :set -XTypeApplications
5151
ghci> :t (<$>) @IO
5252
(<$>) @IO :: (a -> b) -> IO a -> IO b
5353
```
5454

55-
2つの引数と戻り値:
55+
上記のように`<$>`の型変数`f``IO`に絞ったバージョンを作ると、2つの引数と戻り値は次のような型となります:
5656

5757
- 第1引数(左辺)の`a -> b`: 普通の(入出力を行わない)関数 `a -> b`
5858
- 第2引数(右辺)の`IO a`: 第1引数の関数の引数と、同じ型の値を返す`IO`(命令)。
5959
- 戻り値の`IO b`: 第1引数(左辺)の関数と、同じ型の値を返す命令。
6060

61-
`IO a``a -> b``IO b`に変換する演算子である、というのが伝わるでしょうか?
62-
`length <$> getLine`の例に当てはめると、`getLine`で「ユーザーから入力してもらった文字列」に対して、`length`関数を実行して得た文字列の長さを返す`IO`(命令)を作っていますね!
61+
`IO a``a -> b``IO b`に変換する演算子である、というのが伝わるでしょうか?`length <$> getLine`の例に当てはめると、`getLine`で「ユーザーから入力してもらった文字列」に対して、`length`関数を実行して得た文字列の長さを返す`IO`(命令)を作っていますね!
6362

64-
```
63+
```haskell
6564
length <$> getLine
6665
^^^^^^ ^^^^^^^
6766
[Char] -> Int IO [Char]
@@ -76,25 +75,20 @@ ghci> :t (<$>) @IO
7675
f b
7776
```
7877

79-
ここで重要なのは、第1引数である関数はあくまでも`a -> b`という型の、「普通の関数」として**扱われる**、という点です。
80-
`<$>``IO`を返す関数`a -> IO b`に対して使うと、**型エラーにはならず**、妙な動作になってしまう場合があるのでご注意ください。
78+
ただし厳密に言えば、第1引数である関数はあくまでも`a -> b`という型の、「普通の関数」として**扱われる**、という点にはご注意ください。`<$>``IO`を返す関数`a -> IO b`に対して使うと、**型エラーにはならず**、妙な動作になってしまう場合があります。例えば、`putStrLn <$> getLine`とGHCi上で書いた場合...
8179

82-
例えば、`putStrLn <$> getLine`とGHCi上で書いた場合...
83-
84-
```
80+
```haskell
8581
ghci> putStrLn <$> getLine
8682
aaaaa -- ここはユーザーによる入力
8783
```
8884

89-
`putStrLn =<< getLine`と書いたときと異なり、入力した文字列が`putStrLn`によって出力されなかったことにお気づきでしょうか?
90-
そう、`getLine`が実行されてユーザーから入力を得たものの、なぜかそれを受け取ったはずの`putStrLn`は実行されなかったのです...😱
85+
`putStrLn =<< getLine`と書いたときと異なり、入力した文字列が`putStrLn`によって出力されなかったことにお気づきでしょうか?そう、`getLine`が実行されてユーザーから入力を得たものの、なぜかそれを受け取ったはずの`putStrLn`は実行されなかったのです...😱
9186

9287
#### なぜ`putStrLn <$> getLine`と書いても`putStrLn`は実行されないのか、あるいは型注釈を付けようという話
9388

94-
この理由については、難しい話になるので節を分けます。
95-
今回の課題を解くだけの目的では必要ないばかりか、あなたの今後のHaskeller人生で絶対に必要な知識でもないので、わからなかったら適当に飛ばしてください。
89+
この理由については難しい話になるので節を分けます。今回の課題を解くだけの目的では必要ないばかりか、あなたの今後のHaskeller人生で絶対に必要な知識でもないので、わからなかったら適当に飛ばしてください。
9690

97-
```
91+
```haskell
9892
ghci> :t putStrLn <$> getLine
9993
putStrLn <$> getLine :: IO (IO ())
10094
```
@@ -108,32 +102,25 @@ putStrLn <$> getLine :: IO (IO ())
108102
- `IO`に対する`<$>``(a -> b) -> IO a -> IO b`という型なので、
109103
- 型変数`a``[Char]`、型変数`b``IO ()`が代入された。
110104

111-
そう、`IO ()`も、実態は単なる普通の型なので、普通に型推論され、普通に型変数に代入されます。
112-
これまで扱ってきた`Integer``Bool``[a]`などと変わらない、普通の型なのです。
105+
そう、`IO ()`、実態は単なる普通の型なので、普通に型推論され、普通に型変数に代入されます。これまで扱ってきた`Integer``Bool``[a]`などと変わらない、普通の型なのです。
113106

114-
型と言うことは何らかの値を表しているはずです。これまではっきりと説明してきませんでしたが、一体`IO`型の値は何を表しているのでしょう?
115-
`IO`は「命令」を表すオブジェクトです。
107+
型と言うことは何らかの値を表しているはずです。これまではっきりと説明してきませんでしたが、一体`IO`型の値は何を表しているのでしょう?
116108

117-
例えるなら、ほかのプログラミング言語で言うところの「関数オブジェクト」に近いです。
118-
PythonやJavaScriptにおける関数オブジェクト、Rubyで言えば`Proc`クラスのオブジェクト、Javaにおける`Callable`インターフェースを実装したオブジェクトなどなど
109+
`IO`は「命令」を表すオブジェクトです。例えるなら、ほかのプログラミング言語で言うところの「関数オブジェクト」に近いです。PythonやJavaScriptにおける関数オブジェクト、Rubyで言えば`Proc`クラスのオブジェクト、Javaにおける`Callable`インターフェースを実装したオブジェクトなどなど。
119110

120-
ただし、ほかのプログラミング言語で言うところの、「関数オブジェクト」と異なり、引数を受け取りません。
121-
Haskellでは引数を受け取るのは、あくまでも普通の関数`a -> b`の役目なのです。
122-
`putStrLn`も引数に当たる`[Char]``[Char] -> IO ()`という型の「普通の関数」の引数として受け取っていますね。
111+
ただし、ほかのプログラミング言語で言うところの、「関数オブジェクト」と異なり、引数を受け取りません。Haskellでは引数を受け取るのは、あくまでも普通の関数`a -> b`の役目なのです。`putStrLn`も引数に当たる`[Char]``[Char] -> IO ()`という型の「普通の関数」の引数として受け取っています。
123112

124-
一方、普通の関数`a -> b`は、`IO`のように入出力処理が出来ません。
125-
なので入出力処理の部分は`IO`に任せているのです。
113+
一方、普通の関数`a -> b`は、`IO`のように入出力処理が出来ません。なので入出力処理の部分は`IO`に任せているのです。
126114

127-
結果、ほかのプログラミング言語における「関数オブジェクト」は、自由に入出力ができる、関数の戻り値(や引数)になることができる、という点で、Haskellの`IO`と似ています。
128-
従って、「`IO`は引数を受け取らない関数オブジェクト」というイメージで捉えてください。
115+
したがって、ほかのプログラミング言語における「関数オブジェクト」は、「自由に入出力ができる」、「関数の戻り値(や引数)になることができる」、という点で、Haskellの`IO`と似ています。「`IO`は引数を受け取らない関数オブジェクト」と捉えてください。
129116

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

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

136-
```
121+
先程の`putStrLn <$> getLine`を、今度こそ全部実行してみましょう:
122+
123+
```haskell
137124
ghci> :t putStrLn <$> getLine
138125
putStrLn <$> getLine :: IO (IO ())
139126
ghci> returnedAction <- putStrLn <$> getLine

0 commit comments

Comments
 (0)