Skip to content

Commit 87802ce

Browse files
committed
Rewrite case of tuple, plus some minor updates
1 parent e021141 commit 87802ce

File tree

2 files changed

+24
-35
lines changed

2 files changed

+24
-35
lines changed

examples/2020/strict-gotchas/tuple.hs

Lines changed: 0 additions & 12 deletions
This file was deleted.

preprocessed-site/posts/2020/strict-gotchas.md

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Haskellは他の多くのプログラミング言語と異なった特徴を備
2121

2222
このうち、`StrictData`は比較的リスクが少なく大変有用<small>(もはや標準であって欲しいぐらい)</small>という声をよく聞きますが[^strictdata-sample]`Strict`については様々な問題点があることが知られています。今回はその各種問題点をまとめて共有することで、思い切って`Strict`を有効にするときに参考になる情報を提供したいと思います!
2323

24-
[^strictdata-sample]: 例えばfumievalさんによる[この記事](http://fumieval.hatenablog.com/entry/2015/12/10/200630)より:> もっとも、日常ではここまで気にしなければいけない場面は少ないので、ほとんどの場合は気にせず感嘆符をつけて大丈夫だろう。GHC 8.0からは、全フィールドをデフォルトで正格にする`StrictData`という拡張が入るため、こちらを使おう
24+
[^strictdata-sample]: 例えばfumievalさんによる[この記事](http://fumieval.hatenablog.com/entry/2015/12/10/200630)より:もっとも、日常ではここまで気にしなければいけない場面は少ないので、ほとんどの場合は気にせず感嘆符をつけて大丈夫だろう。GHC 8.0からは、全フィールドをデフォルトで正格にする`StrictData`という拡張が入るため、こちらを使おう
2525

2626
# 前提知識とその参考資料
2727

@@ -47,7 +47,7 @@ cd blog/examples/2020/strict-gotchas
4747
stack exec runghc -- <これから紹介するコードのファイル>.hs
4848
```
4949

50-
実際に試すときは`-XStrict`というオプションを`runghc`に付けた場合と付けなかった場合両方で実行して、違いを確かめてみてください。
50+
実際に試すときは`--ghc-arg=-XStrict`というオプションを`runghc`に付けた場合と付けなかった場合両方で実行して、違いを確かめてみてください。
5151

5252
なお、使用したGHCのバージョンは8.10.1で、OSはWindows 10 ver. 1909です。
5353

@@ -188,7 +188,7 @@ f = map (+ 3) . filter (> 2)
188188

189189
しかし、`Strict`を有効にしたモジュールでこのような書き換えを行うと、`f`の挙動が変わってしまいます。引数`.`を使って書き換える前は、引数`xs`に言及していたところ`.`を使って引数`xs`に言及しなくなったからです。`filter``map``Strict`拡張を有効にしたモジュールで定義されているわけではないので、引数を正格に評価しないんですね。結果、こうした書き換えによって、**`Strict`拡張を有効にしていても意図せず遅延評価してしまう**、というリスクがあるので、リファクタリングの際はくれぐれも気をつけてください[^list]。ざっくりまとめると、`Strict`拡張を有効にしているモジュールでは、「引数や変数を宣言することすなわちWHNFまで評価すること」、あるいは「引数や変数を宣言しなければ、評価されない」と意識しましょう。
190190

191-
[^list]: もっとも、この場合引数はリストでしょうから、WHNFまでのみ正格評価するメリットは少なそうですが。
191+
[^list]: もっとも、この例では引数はリストでしょうから、WHNFまでのみ正格評価するメリットは少なそうですが。
192192

193193
ちなみに、`referArgs`における`_`のように「`Strict`拡張を有効にした場合さえ、使用していない引数が評価されてしまうのは困る!」という場合は、引数名の前にチルダ`~`を付けてください。
194194

@@ -199,40 +199,41 @@ referArgs x ~_ = x
199199

200200
# Case 3: 内側のパターンはやっぱりダメ
201201

202-
サンプル: [tuple.hs](https://github.com/haskell-jp/blog/blob/master/examples/2020/strict-gotchas/tuple.hs)
202+
サンプル: 今回はGHCiですべて紹介するのでサンプルはありません。
203203

204204
続いては、`Strict`拡張のドキュメントでも触れられている、入れ子になったパターンマッチにおける問題を紹介します。一言で言うと、`let (a, b) = ...`のような、データ構造<small>(この場合タプルですね)</small>の「内側」に対するパターンマッチは、`Strict`拡張を有効にしていても正格に評価しないよ、という話です。
205205

206-
例えば、下記のコードを`Strict`拡張付きで実行しても、パターンマッチしている`a``b`ともに代入した時点では正格評価されず、`error "a"``error "b"`による例外はいずれも発生しません。
206+
例えば、下記のコードを`Strict`拡張付きで実行しても、パターンマッチしている`a``b`ともに代入した時点では正格評価されず、`error "a"``error "b"`による例外はいずれも発生しません。次のコードをGHCiで試してみてください。
207207

208208
```haskell
209-
(a, b) = (error "a", error "b")
209+
> :set -XStrict
210+
> (a, b) = (error "a", error "b")
211+
-- 何も起きない
210212
```
211213

212-
これは、タプルの値コンストラクター`(,)`が<small>(`StrictData`やStrictness flagを用いないで定義されているので)</small>各要素を遅延評価することとは**関係なく**、各要素を正格評価する値コンストラクターであっても同様です
214+
先ほどの節における「`Strict`拡張を有効にしているモジュールでは、『引数や変数を宣言することすなわちWHNFまで評価すること」』、あるいは『引数や変数を宣言しなければ、評価されない』と意識しましょう」という主張を真に受けてしまうと、意図せず遅延評価させてしまい、ハマりそうです😰。⚠️繰り返しますが「**内側のパターンにおける変数は正格評価されない**」ということも意識してください
213215

214-
```haskell
215-
data MyTuple a b = MyTuple !a !b deriving Show
216+
一方、`StrictData`やStrictness flagを用いるなどして、各要素を正格評価するよう定義した値コンストラクターでは、ちゃんと評価して例外を発生させます。
216217

217-
MyTuple a b = MyTuple (error "a") (error "b")
218+
```haskell
219+
> :set -XStrict
220+
> data MyTuple a b = MyTuple a b deriving Show
221+
> let MyTuple a b = MyTuple (error "a") (error "b")
222+
*** Exception: b
223+
CallStack (from HasCallStack):
224+
error, called at <interactive>:10:40 in interactive:Ghci7
218225
```
219226

220-
先ほどの節における「`Strict`拡張を有効にしているモジュールでは、『引数や変数を宣言することすなわちWHNFまで評価すること」』、あるいは『引数や変数を宣言しなければ、評価されない』と意識しましょう」という主張を真に受けてしまうと意図せず遅延評価させてしまい、ハマりそうです😰。⚠️繰り返しますが「**内側のパターンにおける変数は正格評価されない**」ということも意識してください
227+
`Strict`拡張を有効にすると`StrictData`も自動的に有効になるので、👆における`MyTuple`値コンストラクターは各要素を正格評価するようになったわけです。なので`Strict`拡張を有効にしたモジュールにおいて、なおかつそこで定義した型で完結している限りは平和でしょう
221228

222-
以上のことを試していただくために、サンプルコードでは`Strict`を有効にしてもしなくても、結果が変わらないサンプルを用意しました<small>(コードは👆の例に`main`がくっついたのとあまり変わらないので解説は省きます)</small>。以下、実行例を
229+
ただし、GHCiで試す場合に特に注意していただきたいのですが、GHCiで**`let`をつけないでパターンマッチした場合は正格評価されない**、という点に注意してください。`let`をつけないとトップレベルでの定義と見なされるからです。[Strict拡張のドキュメント](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#strict-by-default-pattern-bindings)にも、「Top level bindings are unaffected by `Strict`」とありますとおり、トップレベルでの定義は例外扱いされているのです
223230

224-
```bash
225-
> stack exec runhaskell ./tuple.hs
226-
"Default tuple"
227-
"Default tuple in MyTuple1"
228-
"Other value in MyTuple1"
229-
230-
> stack exec -- runghc --ghc-arg=-XStrict ./tuple.hs
231-
"Default tuple"
232-
"Default tuple in MyTuple1"
233-
"Other value in MyTuple1"
231+
```haskell
232+
> :set -XStrict
233+
> data MyTuple a b = MyTuple a b deriving Show
234+
> MyTuple a b = MyTuple (error "a") (error "b")
235+
-- 何も起きない
234236
```
235-
236237
# Case 4: `foldr`に渡す関数
237238

238239
サンプル: [stackoverflow-foldr.hs](https://github.com/haskell-jp/blog/blob/master/examples/2020/strict-gotchas/stackoverflow-foldr.hs)

0 commit comments

Comments
 (0)