これらのケースの多くは正の数しか処理できないため、テイク/リターンの一般的なパターンInt
(つまりByteString.hGet
、およびData.List.length
)は、強く記述したタイプを使用するHaskellパターンとは反対のようです。を使用する方が良いのではないでしょうかWord
、またはこれらの関数が部分的である理由がありInt
ますか?
3 に答える
Haskell型システムの表現力により、ユーザーは定義したエンティティに正確な型を割り当てることができます。しかし、ベテランのHaskellersは、究極の型精度(Haskell型システムの現在の制限を考えると常に達成できるとは限らない)と利便性のバランスをとらなければならないことを容易に認めます。要するに、正確な型はある意味でしか役に立ちません。その点を超えて、彼らはしばしば、ほとんどまたはまったく利益をもたらさずに余分な官僚主義を引き起こします。
例を挙げて問題を説明しましょう。階乗関数を考えてみましょう。n
1より大きいすべての場合、の階乗n
は偶数であり、1の階乗はそれほど面白くないので、それを無視しましょう。したがって、Haskellでの階乗関数の実装が正しいことを確認するために、符号なしの偶数の整数のみを表すことができる新しい数値型を導入したくなるかもしれません。
module (Even) where
newtype Even = Even Integer
instance Num Even where
...
fromInteger x | x `mod` 2 == 0 = Even x
| otherwise = error "Not an even number."
instance Integral Even where
...
toInteger (Even x) = x
このデータ型を、コンストラクターをエクスポートしないモジュール内に封印して抽象化し、のインスタンスであるすべての関連する型クラスInt
のインスタンスにします。これで、階乗に次の署名を付けることができます。
factorial :: Int -> Even
確実のタイプはfactorial
、それが戻ると言った場合よりも正確ですInt
。しかしfactorial
、このような型で定義するのは非常に面倒です。これは、(偶数または奇数)Int
にanを乗算し、 Even
andを生成するバージョンの乗算が必要だからですEven
。さらに、クライアントコードでtoInteger
の呼び出しの結果に、外部の呼び出しを導入する必要がある場合がfactorial
あります。これは、ほとんど利益が得られないため、混乱やノイズの大きな原因となる可能性があります。さらに、これらすべての変換機能は、パフォーマンスに悪影響を与える可能性があります。
もう1つの問題は、新しい、より正確な型を導入するときに、すべての種類のライブラリ関数を複製しなければならないことがよくあることです。たとえば、List1 a
空でないリストのタイプを導入する場合Data.List
は、すでに提供されている機能の多くを再実装する必要がありますが、それ[a]
だけです。確かに、これらの関数をListLike
型クラスのメソッドにすることができます。しかし、あなたはすぐにあらゆる種類のアドホック型クラスや他の定型文になってしまい、やはりあまり利益はありません。
最後のポイントはWord
、の符号なしバリアントであると見なすべきではないということですInt
。Haskellレポートは、実際のサイズを指定せずに残し、このタイプが[− 2 29、2 29 −1 ]Int
の範囲の整数を表現できることを保証するだけです。この型は、幅が指定されていない符号なし整数を提供すると言われています。準拠する実装において、aの幅が。の幅に対応することは保証されていません。Word
Word
Int
私は過度のタイプの増殖を防ぐことを強調していますが、あるタイプNatural
のナチュラルを導入することは良いことだと認識しています。しかし、最終的には、Haskellが、、、およびさまざまなタイプに加えてInt
、自然数専用のタイプを持つべきかどうかは、 主に好みの問題です。そして、現在の状況は、おそらく非常に大部分が歴史の偶然に過ぎません。Integer
Word*
Cと同じ理由が当てはまります。より正確な型を使用する理由は、間違いを防ぐためです。この場合、意味のない負の数を使おうとするなどの間違い。ただし、 Cの場合Word
のように、オーバーフローまたはアンダーフローの動作はunsigned int
ラップアラウンドです。Word
(または)が期待される場所で負の数を使用しようとするとunsigned int
、コンパイラーがあなたに怒鳴ったり、実行時に例外が発生したりすることはありません。あなたは大きな正の数を得る。これは、他の(「正当な」)大きな正の数と区別する方法がありません。
これを見てください:
Prelude Data.Word> -1 :: Word
4294967295
間違いを犯すことを不可能にする代わりに、あなたはそれらを検出することを不可能にしました。Int
(および)を使用int
すると、少なくとも手動で負の値をチェックする可能性があります。とを使用するWord
とunsigned int
、何もありません。
価値があるのは、例外をスローすることによってオーバーフローまたはアンダーフローに反応する符号なし型です。それでも間違いを不可能にすることはありませんが、間違いを見つけやすくなります。ただし、パフォーマンスが低下します。*コンパイル時に除外できるかどうかはわかりませんが、簡単ではないようです。
*少なくとも、x86には追加の命令が必要です-すべての操作の後に!-オーバーフローまたはアンダーフローが発生したかどうかを確認します。「無料」でそれを行うアーキテクチャがあるかどうかはわかりませんが、それは素晴らしいことです。または、浮動小数点数の場合のように、表現できない値を示すために使用される(おそらく最も負の数の代わりに)区別されたNaN値...
私の最初の推測では、符号なし演算には、注意を払っていない場合に愚かなバグを引き起こすいくつかの問題があります。
Prelude Data.Word> let x = 0 :: Word in if x - 1 > x then "Ouch" else "Ohayoo"
"Ouch"
正しいと思われるポリモーフィック関数にいくつかの問題があります。
Prelude Data.Word> let f acc x y = if x <= y then f (acc + 1) x (y - 1) else acc
Prelude Data.Word> :t f
f :: (Num a1, Num a, Ord a) => a1 -> a -> a -> a1
標準の長さ関数を使用する:
Prelude Data.Word> let len' x = if null x then 0 else 1 + len' (tail x) :: Int
Prelude Data.Word> :t len'
len' :: [a] -> Int
Prelude Data.Word> f 0 0 (len' [1,2,3])
4
そして、長さ関数を使用してreturing Word
:
Prelude Data.Word> let len x = if null x then 0 else 1 + len (tail x) :: Word
Prelude Data.Word> :t len
len :: [a] -> Word
Prelude Data.Word> f 0 0 (len [1,2,3])
...diverges...
そしてもちろん、これらの関数がWord
の代わりに返された場合は、他の一般的な場所で使用するために、 sをsにInt
変換し続ける必要があります。Word
Int