例外が起きるような状況は確かに減らせる。でも減らしていくと、あるところを境に急激にコーディング量が増える。 head :: [a] -> Maybe a, floor :: a -> Maybe b はなんとか譲るとしても、div :: a -> a -> Maybe a なんてとてもやってられない。 パフォーマンスにも致命的な影響が出る。だから標準ライブラリは div :: a -> a -> a とか head :: [a] -> a で妥協してあるわけだ。 実際には絶対に空のリストではないと仮定できる場合もあるから、 head :: [a] -> Maybe
確かに NonEmpty なリストを使えば head でコケるような心配はなくなる。俺もどんどん使ったほうがいいと思う。 だけど、undefined だってあるし、bottom ( x = x とかね ) があるから、どんなに型システムで頑張っても絶対に落ちないということはない。 そもそも、クラッシュしないほうのバグは型システムではどうにもならない。
バグを抑えやすい、かー (スコア:2)
Haskellを触ってみた感じ、デバッグが難しそうなんだけど、バグ自体が少ないから構わないって言うのかな?
Re: (スコア:4, 参考になる)
バグ自体が少ないと言っているのは,
・プロジェクト数が少ない
・実行時バグの余地が少ない
と2通りあります.
前者は仕方のないことで,
別に批判されるべきことじゃありませんよね.
後者については型システム(およびコンパイラ)が,
性質を静的に保障してくれることが多く,
これは開発のうえで非常に強力です.
デバッグの難しさについては,
ちょっと真面目にすごいH [amazon.co.jp]で勉強した私はそうは思いません.
まず, テストのしやすさは手続型言語よりも容易です.
また, 従来再現しにくいバ
Re: (スコア:0)
すごいH本にデバッギングの解説ってあったっけ?あんまり精読してないので、見落としたかも。
今ちょっと日本語版が手元にないので英語版 [learnyouahaskell.com](無料で全部読めるよ!)の目次見たけど、
それっぽいのが見当たらない
Re: (スコア:2)
デバッギングの解説はないですが,
実行中の挙動について表示させる例はありましたよね.
そう, 12章の"Walk in the line"の例です.
失敗するかもしれない関数landLeft,landRightの仕様をMaybeという文脈つけて変更していました.
でも実はこれだけじゃなくて,
純粋な関数であったとしても, トレースを使えます.
山本先生のブログ記事 [hatena.ne.jp]
究極的には, プログラムがstuckするのは,
パターンマッチに転けた場合が多いです.
そのときパターンマッチした際の値を得ることで,
原因は大体わかりますよね
Re: (スコア:5, 参考になる)
自分の書いたコードのパターンマッチングで落っこちるとちゃんと行番号が出てくるし、オプションつければ漏れがあっても警告出るから問題ないね。
困るのはゼロ除算や head [] で落ちたりとか、クラッシュせずに誤った結果を返してくるときとかかな。
head [] なんて、エラーメッセージは "Prelude.head: empty list" だけだからね。どうやって原因を突き止めろと。
trace を埋め込む場所がわかるなら、バグは半分見つけたようなもので……。
自分はその山本さんの記事も読んだことあるんだけど、
あと、例外がどこから来たのか知りたい場合は、GHC のデバッガを使うといいでしょう。
という一文にがっかりした覚えが。その GHCi のデバッガが、ほとんど役に立たないんだよ……。
だいたい GHCi (Haskell 処理系のデファクトスタンダードだね)のデバッガのマニュアルがこんな調子なんだよ。
プログラムをデバッグしている時にしばしば問われる問いの一つに、「どうやってここに来たんだ?」というものがある。伝統的な命令的デバッガは通常何らかのスタックトレースの機能を持っていて、それを使ってアクティブな関数呼び出しのスタック(字句的呼び出しスタックと呼ばれることもある)を確認することができる。このスタックによって、現在の地点に至るまでのコード上の道のりが分かる。残念なことに、これをHaskellで用意するのは難しい。正格な言語と違って、実行は深さ優先ではなく必要に応じて進むからである。
も
Re: (スコア:0)
場当たり的なコメントで申し訳ないですが,
head []はパターンマッチで書く方が筋が良い.
(listToMaybe的なものをつかっても一緒)
ゼロ除算はcatchするよりも,
Integerを使うか最初から除算に気をつければいい.
失敗するかもしれない演算なんだから.
手続き型言語の例外は最初から想定しているもので,
それぞれcatchすればいいよね, という風潮だけど,
関数型言語の例外は, 発生しないように書くという前提.
究極的には関数型言語を好きになるかどうかって,
例外が発生しうる範囲が非常に限定されているんだから, 最初から避けて設計するよね,
というのを受け入れるか否かの違いだと思う.
Re: (スコア:0)
最初から除算に気をつければいい.
「気を付ける」っていうのは具体的にどうすればいいの?
例外が起きるような状況は確かに減らせる。でも減らしていくと、あるところを境に急激にコーディング量が増える。
head :: [a] -> Maybe a, floor :: a -> Maybe b はなんとか譲るとしても、div :: a -> a -> Maybe a なんてとてもやってられない。
パフォーマンスにも致命的な影響が出る。だから標準ライブラリは div :: a -> a -> a とか head :: [a] -> a で妥協してあるわけだ。
実際には絶対に空のリストではないと仮定できる場合もあるから、 head :: [a] -> Maybe
Re:バグを抑えやすい、かー (スコア:0)
転ける可能性がある計算はMaybe使えばいいのは譲れるわけですよね?
貴方が言っているのは, コケないことが自明な場合には,
それを書くことは無駄な手間とオーバーヘッドになるんじゃないか, でしょうか?
# だからそういう意味ではdivであろうとheadであろうと問題は一緒で,
# コケないはずのものに手間をかけたくない, と
だったら型システムで保障してやりましょう.
絶対コケないはずなんでしょ? だったらできます.
例えば, Data.List.NonEmptyモジュールを使えば,
データを作る側でNonEmptyであることを保障しますよ
Emptyである可能性があればコンパイルでコケます.
つまり, 何かの関数がリストを返すとき,
それが空になるパスがある限り,
NonEmptyなリストを返す関数としてコンパイルできません.
Re: (スコア:0)
確かに NonEmpty なリストを使えば head でコケるような心配はなくなる。俺もどんどん使ったほうがいいと思う。
だけど、undefined だってあるし、bottom ( x = x とかね ) があるから、どんなに型システムで頑張っても絶対に落ちないということはない。
そもそも、クラッシュしないほうのバグは型システムではどうにもならない。
それに、NonEmpty のような特殊化された型を検討するのはいいんだけど、その方向で安全性を保証しようとするときりがない。
リストのインデックスのために自然数型を定義すべきだろうか?除算を安全にするために『ゼロ以外の数』の型を定義すべ