toFloat :: (Floating a) => String -> a
toFloat s = read s :: Float
main = print (toFloat "1")
エラーが発生します:
Could not deduce (a ~ Float)
from the context (Floating a)
基本的なものが欠けていることは確かですが、toFloatは常にFloatを返す必要があり、FloatはFloatingを意味するはずです。
toFloat :: (Floating a) => String -> a
toFloat s = read s :: Float
main = print (toFloat "1")
エラーが発生します:
Could not deduce (a ~ Float)
from the context (Floating a)
基本的なものが欠けていることは確かですが、toFloatは常にFloatを返す必要があり、FloatはFloatingを意味するはずです。
Floating
型アノテーションは、結果が呼び出し元が必要とするクラスの任意のインスタンスになることを約束します。実装は「何を知っていますか?それがどんなタイプでもあり得るというその約束を気にしないでください-ただそれを「」にしましょうFloat
」と言います。
次に、コンパイラがやって来て、「おっと!約束したタイプを返さない」と言います。型署名と実装を一致させるために本当に一生懸命努力したことを除いて。それは、「これが何らかの形で制約されていて、それa
が常にと同じであるFloat
ならば、これは正しいだろう」と述べた。それは本当にあなたのコードが正しい方法を見つけたかったのです。さて、そのような制約を書く方法は~
、型の等式演算子を使用することです。の制約は、「は」と同じタイプである(a ~ Float)
ことを意味します。そのため、コンパイラは型シグネチャで指定したコンテキストをチェックし、その制約を見つけることができません。また、型アノテーションと実装を連携させる方法が不足し、あきらめてエラーを報告します。a
Float
残念ながら、コードを機能させるために多大な労力を費やしているため、報告されるエラーは少し不透明です。ほんのわずかな変更で、1つの小さな制約を追加するだけで、それは正しくなります。したがって、その制約が存在しなかったことが報告されます。しかし、なぜそれがその制約を探していたのかは報告されていないため、これまでに見たことがない場合は全体が少し不明確になります。
この質問またはそれに非常によく似たものが定期的に尋ねられます。(それは不満ではありません、私はあなたがこれによって混乱している唯一の人ではないことを指摘しているだけです。)
オブジェクト指向言語では、「この関数はXを実装するものを返す」と言うことができます。関数は、実際にXを実装している限り、戻りたいと感じるものは何でも返すことができます。
Haskellはそのようには機能しません。「この関数はXを実装するものを返す」と言う場合、関数はXを実装する可能性のあるすべての型を生成できる必要があります。
主な違いは次のとおりです。オブジェクト指向言語では、関数は(指定された制約内で)返すタイプを決定します。Haskellでは、呼び出し元が返すタイプを決定します(ここでも、規定された制約の範囲内で)。
この重要な違いを理解すれば、残りはかなり自明です。
繰り返しますが、多くの人がこの部分を誤解しているようです。それはVFAQのように見えるので、チュートリアルなどでもっと言及する必要があります...
typeclasstoFloat
に属する任意の型を返すことができると言っていますが、それをに制限しています。これは間違っています。関数はで多形であるため、のインスタンスを返すことはできません。すべてのインスタンスで機能するはずです。Floating
Float
a
Floating
そうでなければあなたはこれを理解することができます
toFloat :: (Read a,Floating a) => String -> a
toFloat s = read s
ghciで
*Main> :t toFloat "12.1"
toFloat "12.1" :: (Floating a, Read a) => a
*Main> :t (toFloat "12.1" :: Float)
(toFloat "12.1" :: Float) :: Float
*Main> :t (toFloat "12.1" :: Double)
(toFloat "12.1" :: Double) :: Double
typeclassに属する型を返すため、関数の適用後に明示的な型シグネチャを提供Floating
することで、任意の型(に属する)に変換できるはずです。Floating
一方、明示的に戻ってきたときのケースを思い出してください。明示的な変換なしでは発生しないため、この関数Float
に期待しているとだけ言うことはできません。Double
あなたの仮定がどれほど恐ろしいかを理解する別の方法は、関数を考慮することですread
read :: Read a => String -> a
ここであなたによると、のインスタンスがあるInt
ので、すべてに対してsayを返すことができます。これで、次のようなことをするとどうなるかを理解できますInt
Read
read "12" + (1.2 :: Double)
これはあなたがそれを持つことができるのと同じくらい簡単です:
-- The simplest.
toFloat :: String -> Float
toFloat = read
-- More generalized.
toFloat' :: (Floating a, Read a) => String -> a
toFloat' = read