Skip to content

Commit

Permalink
Simplify ex18
Browse files Browse the repository at this point in the history
  • Loading branch information
igrep committed May 26, 2024
1 parent 43fe6b0 commit 51ef49b
Showing 1 changed file with 9 additions and 90 deletions.
99 changes: 9 additions & 90 deletions assets/18.md
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,9 @@ isEmptyEntry (Entry { category = "", price = 0 }) = True
isEmptyEntry otherEntry = False
```

2行目の`isEmptyEntry (Entry { category = "", price = 0 }) = True`という行に注目してください。先程`entry = Entry { category = "", price = 0 }`と書いて`entry`を作るのに使用した`Entry { category = "", price = 0 }`という式が、ここでも使用されているのがわかるでしょうか?そう、パターンマッチでマッチさせる式を指定するのは、値を定義するのに用いる式と、全く同じ構文なのです。
2行目の`isEmptyEntry (Entry { category = "", price = 0 }) = True`という行に注目してください。先程`entry = Entry { category = "", price = 0 }`と書いて`entry`を作るのに使用した`Entry { category = "", price = 0 }`という式が、ここでも使用されているのがわかるでしょうか?そう、パターンマッチでマッチさせる式を指定するのは、値を定義するのに用いる式と、全く同じ構文なのです[^2]

[^2]: 厳密な話をすると、課題hoge(8?)で紹介した「asパターン」などのように、パターンマッチで使えるけど値の定義では使えない構文があるので、完全に一致するわけではありません。

このことは、パターンに変数が含まれている場合も同じです。`Entry`型の`category``cat``price``pri`に割り当てるときのパターンマッチを例にします:

Expand All @@ -999,59 +1001,15 @@ ghci> entry = Entry { category = cat, price = pri }

以上の性質は、これまで紹介したものや、これ以降に紹介するパターンマッチの構文でも全く同じことが言えます。ぜひ覚えておいてください。

#### タプルに対する関数の引数でのパターンマッチ

便利なサンプルが[`Prelude`モジュール][6]にいくつかある。
以下はソースコードからの抜粋:

[6]: https://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#g:3

```
fst :: (a, b) -> a
fst (x, _) = x
snd :: (a, b) -> b
snd (_, y) = y
```

課題8で紹介したとおり、これらは`Prelude`モジュールに入っているので、何も`import`しなくても使える
いずれにせよ、パターンマッチというのは引数の宣言や変数への代入など、値を変数に割り当てるあらゆる場面で使えます。この課題を含め、ここまでで勉強したパターンマッチについての原則をまとめました:

おまけ: `curry``uncurry`
#### パターンマッチの原則まとめ

`curry`は「(サイズ2の)タプルを受け取る関数」を(Haskellの世界で普通の、カリー化された)2引数関数に変換する。
パターンマッチについては課題hoge(9?)でも触れ、リストやタプルをパターンマッチする際の構文を紹介しました。この課題で学習したことは、当然リストやタプルをパターンマッチするときにも応用できます。改めて、以下の原則と併せて復習してみてください:

```
curry :: ((a, b) -> c) -> a -> b -> c
curry f x y = f (x, y)
```

`uncurry`はその逆。ここでタプルに対するパターンマッチが出てくる。

```
uncurry :: (a -> b -> c) -> ((a, b) -> c)
uncurry f (x, y) = f x y
```

おそらく`uncurry`の方が実践ではよく使う

典型的な使用例: `Map`方の値を`toList`関数で変換した結果を、そのまま2引数の関数に渡す

`(+)``uncurry`関数でタプルを受け取る関数に変換することで、`toList`結果の値をそのまま使える。

```
ghci> import qualified Data.Map.Strict as M
ghci> numberAndChars = M.fromList [(1, 3), (2, 4)]
ghci> map (uncurry (+)) $ M.toList numberAndChars
[4,6]
```

ラムダ抽象で書き換えると↓と同じ

```
ghci> map (\pair -> fst pair + snd pair) $ M.toList numberAndChars
[4,6]
```
- 原則1: 値コンストラクターで値を組み立てるのと逆のことをするのがパターンマッチ
- リストには例外的に、 `[x, y]` という構文の、特別な値コンストラクターがある。本来は`x : y : []`
- 原則2: 関数の引数を含めた、変数への代入を行うあらゆる場面でパターンマッチは使える

#### ラムダ抽象の引数でのパターンマッチ

Expand Down Expand Up @@ -1079,12 +1037,6 @@ let (divResult, modResult) = divMod numerator denominator
-- ...
```

パターンマッチに関する話をまとめると

- 原則1: 値コンストラクターで値を組み立てるのと逆のことをするのがパターンマッチ。
- リストには `[x, y]` という構文の、特別な値コンストラクターがある。本来は`x : y : []`
- 原則2: 関数の引数を含めた、変数への代入を行うあらゆる場面でパターンマッチは使える。

でも、これは危ない!

```
Expand Down Expand Up @@ -1147,36 +1099,3 @@ ghci> dangerous = \(Just x) -> x
<https://functor.tokyo/blog/2017-07-28-ghc-warnings-you-should-enable>

[8]: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-warnings.html#ghc-flag--Wall

## HLintで改善点をある程度自動で見つける

```bash
shell> cabal install hlint
# あるいは...
shell> stack install hlint
```

ここで紹介していない`TupleSections`というGHCの拡張を使ったものも(GHCの拡張の話を後回しにしたいので敢えて紹介していません... あしからず)。

```bash
shell> hlint assets/16.hs
assets/16.hs:18:14: Warning: Avoid lambda
Found:
\ x y -> x + y
Perhaps:
(+)

assets/16.hs:19:19: Suggestion: Use tuple-section
Found:
\ w -> (w, 1)
Perhaps:
(, 1)
Note: may require `{-# LANGUAGE TupleSections #-}` adding to the top of the file

2 hints
```

HLintのさらに便利な使い方は[素晴らしき HLint を使いこなす][9]を。
「この関数はこのモジュール以外では使わないでください」などというプロジェクト固有のルールを設定することもできます。

[9]: https://haskell.e-bigmoon.com/posts/2018-01-29-awesome-hlint.html

0 comments on commit 51ef49b

Please sign in to comment.