2

Haskell型の強制に頭を包み込もうとしています。つまり、キャストせずに関数に値を渡すことができるのはいつですか、それがどのように機能するかです。これが具体的な例ですが、何が起こっているのかを理解するために、今後使用できるより一般的な説明を探しています。

Prelude> 3 * 20 / 4
15.0
Prelude> let c = 20
Prelude> :t c
c :: Integer
Prelude> 3 * c / 4

<interactive>:94:7:
    No instance for (Fractional Integer)
      arising from a use of `/'
    Possible fix: add an instance declaration for (Fractional Integer)
    In the expression: 3 * c / 4
    In an equation for `it': it = 3 * c / 4

(/)のタイプは分数a => a->a->aです。ですから、リテラルを使って「3 * 20」を行うと、Haskellはどういうわけかその式の結果が分数であると想定していると思います。ただし、変数を使用する場合、その型は割り当てに基づいて整数になるように事前定義されています。

私の最初の質問は、これを修正する方法です。式をキャストする必要がありますか、それとも何らかの方法で変換する必要がありますか?私の2番目の質問は、これは私には本当に奇妙に思えるので、int/float型についてそれほど心配する必要なしに基本的な数学を行うことはできません。これらの間で自動的に変換する明白な方法があるということですが、なぜ私はこれについて考えて対処する必要があるのですか?私はそもそも何か間違ったことをしていますか?

私は基本的に、細かい部分を気にせず、コードをきれいに保つことなく、簡単な算術式を簡単に記述できる方法を探しています。ほとんどのトップレベル言語では、コンパイラは私のために機能します-その逆ではありません。

4

5 に答える 5

13

解決策だけ知りたい方は最後までご覧ください。

あなたはすでにあなた自身の質問にほぼ答えています。Haskell のリテラルはオーバーロードされています。

Prelude> :t 3
3 :: Num a => a

制約(*)もあるのでNum

Prelude> :t (*)
(*) :: Num a => a -> a -> a

これは製品にまで及びます:

Prelude> :t 3 * 20
3 * 20 :: Num a => a

そのため、コンテキストに応じて、必要に応じて、、、、などのタイプIntに特殊化できます。特に、のサブクラスであるため、分割で問題なく使用できますが、制約が強くなり、 クラス になります。IntegerFloatDoubleRationalFractionalNumFractional

Prelude> :t 3 * 20 / 4
3 * 20 / 4 :: Fractional a => a

大きな違いは、識別子cInteger. GHCi プロンプトの単純な let 束縛にオーバーロードされた型が割り当てられない理由は、恐ろしい単型性の制限です。つまり、明示的な引数を持たない値を定義する場合、明示的な型シグネチャを提供しない限り、オーバーロードされた型を持つことはできません。数値型はデフォルトで に設定されIntegerます。

一度cは でIntegerあり、乗算の結果Integerも です。

Prelude> :t 3 * c
3 * c :: Integer

そして、クラスIntegerにいません。Fractional

この問題には 2 つの解決策があります。

  1. 識別子の型もオーバーロードされていることを確認してください。この場合、言うのと同じくらい簡単です

      Prelude> let c :: Num a => a; c = 20
      Prelude> :t c
      c :: Num a => a
    
  2. fromIntegral整数値を任意の数値にキャストするために使用します。

      Prelude> :t fromIntegral
      fromIntegral :: (Integral a, Num b) => a -> b
      Prelude> let c = 20
      Prelude> :t c
      c :: Integer
      Prelude> :t fromIntegral c
      fromIntegral c :: Num b => b
      Prelude> 3 * fromIntegral c / 4
      15.0
    
于 2013-02-03T00:01:24.477 に答える
5

Haskell は、関数に型を渡すときに、ある型を別の型に自動的に変換することはありません。予想される型と既に互換性がある (この場合、強制は必要ありません) か、プログラムがコンパイルに失敗します。

プログラム全体を作成してコンパイルすると、一般に、 int/float 型についてあまり考えなくても「問題なく動作」します。一貫性がある限り (つまり、何かをある場所では Int として扱い、別の場所では Float として扱わないようにします)、制約はプログラムを流れて型を見つけ出します。

たとえば、これをソース ファイルに入れてコンパイルすると、次のようになります。

main = do
    let c = 20
    let it = 3 * c / 4
    print it

その後、すべて問題なく、プログラムを実行すると15.0. からわかるように、GHC はそれがある種の小数であるに違いないこと.0を首尾よく突き止め、すべてを機能させました。明示的な型シグネチャを与える必要はありません。c

c/演算子は整数で定義されていない数学的除算用であるため、整数にすることはできません。整数除算の演算は関数で表されdivます (演算子のように として使用できますx `div` y)。これが、プログラム全体であなたをつまずかせている原因ではないでしょうか? /残念ながら、他の多くの言語で数学的除算や整数除算が行われる状況に慣れている場合、これはつまずいて学ばなければならないことの 1 つにすぎません。

インタープリターをいじっているときは、状況がまったくない状態で値をバインドする傾向があるため、混乱が生じます。インタプリタでは、GHCi はlet c = 20自分で実行する必要が3 * c / 4あります。まだ入力していないからです。それが、、、、、、などになるつもりかどうかを知る方法はありませ20ん。IntIntegerFloatDoubleRational

Haskell は数値のデフォルトの型を選択します。そうしないと、特定の 1 つの型の数値に対してのみ機能する関数をまったく使用しないと、あいまいな型変数に関するエラーが常に発生します。これらの既定のルールはモジュール全体を読み取るときに適用され、型に関する他のすべての制約が考慮されるため、これは通常は正常に機能します (使用したことがあるかどうかなど/)。しかし、ここでは他の制約は見られないので、型のデフォルト設定はランクから最初の cab を選択しcInteger.

そして、GHCi に を評価するように頼んだ時点3 * c / 4では手遅れです。cは であるIntegerため、そうである必要3 * cがあり、Integerは をサポートしていません/

したがって、インタプリタでは、そうです。バインドに明示的な型を指定しないletと、特に数値型の場合、GHC が間違った型を選択することがあります。その後、GHCi が選択した具体的な型によってサポートされている操作に固執しますが、この種のエラーが発生した場合は、いつでも変数を再バインドできます。例えばlet c = 20.0

しかし、あなたの実際のプログラムでは、問題は単にあなたが望んでいた操作div/.

于 2013-02-03T00:04:39.270 に答える
3

この点、Haskell は少し特殊です。はい、一緒に整数に分割することはできませんが、問題になることはめったにありません。

その理由は、型クラスを見ると、リテラルを適切な型に変換できるNum関数があるからです。fromIntegralこれと型推論により、問題になるケースの 99% が軽減されます。簡単な例:

newtype Foo = Foo Integer
    deriving (Show, Eq)
instance Num Foo where
   fromInteger  _  = Foo 0
   negate          = undefined
   abs             = undefined
   (+)             = undefined 
   (-)             = undefined 
   (*)             = undefined 
   signum          = undefined

これをGHCiにロードすると

*> 0 :: Foo
   Foo 0

*> 1 :: Foo
   Foo 0

GHCi が生の整数を解析する方法で、かなりクールなことを実行できることがわかります。これには、ここでは説明しませんが、DSL で多くの実用的な用途があります。

次の質問は、Double から Integer に、またはその逆に取得する方法でした。そのための関数があります。

Integer から Double に変換する場合も同様に使用fromIntegerします。なんで?

その型シグネチャは

(Num a) => Integer -> a

また、Double を使用できるので(+)、それらがインスタンスであることがわかりNumます。そしてそこからは簡単です。

*> 0 :: Double
    0.0

パズルの最後のピースはDouble -> Integer. Hoogle 番組で簡単に検索してみます

truncate
floor
round
-- etc ...

検索はお任せします。

于 2013-02-02T23:51:37.930 に答える
0

Haskell の型強制は自動ではありません (というか、実際には存在しません)。リテラル 20 を記述すると、型であると推測されNum a => a(概念的には。私はそれがそのように機能するとは思いません)、それが使用されるコンテキスト (つまり、どの関数に渡すか) に応じてインスタンス化されます。適切な型を使用します(これ以上の制約が適用されない場合、これはIntegerある時点で具体的な型が必要な場合にデフォルトになると思います)。別の種類の が必要な場合は、例のようNumに数値を変換する必要があり(3* fromIntegral c / 4)ます。

于 2013-02-02T23:50:53.423 に答える
0

(/) の型は Fractional a => a -> a -> a です。

整数を除算するには、div代わりに を使用し(/)ます。divの型であることに注意してください。

div :: Integral a => a -> a -> a

ほとんどの最上位言語では、コンパイラが機能しますが、その逆ではありません。

Haskell コンパイラは、これまでに使用した他の言語と同じか、それ以上ではないと私は主張します。Haskell は、おそらく慣れ親しんでいる従来の命令型言語 (C、C++、Java など) とは大きく異なる言語です。これは、コンパイラの動作も異なることを意味します。

他の人が述べているように、Haskell は、あるタイプから別のタイプに自動的に強制することは決してありません。Float として使用する必要がある Integer がある場合は、 で明示的に変換を行う必要がありますfromInteger

于 2013-02-03T00:11:44.027 に答える