2016-11-12 8 views
2

私はGHCiのを使用して、数値強制をテストした:Num型のクラス強制によるインスタンスは、どのようにFractionalに暗黙的にできますか?

>> let c = 1 :: Integer 

>> 1/2 
0.5 

>> c/2 
<interactive>:15:1: error: 
• No instance for (Fractional Integer) arising from a use of ‘/’ 
• In the expression: c/2 
    In an equation for ‘it’: it = c/2 

>> :t (/) 
(/) :: Fractional a => a -> a -> a -- (/) needs Fractional type 

>> (fromInteger c)/2 
0.5 

>>:t fromInteger 
fromInteger :: Num a => Integer -> a -- Just convert the Integer to Num not to Fractional 

を私はテンキー(fromIntegerはタイプfromInteger :: Num a => Integer -> aを持っている)に整数型に変換するfromInteger機能を使用することができますが、私はどのようにタイプNumを変換することができることを理解することはできません暗黙的にFractionalに?インスタンスがNum型を持つ場合、それがFractionalタイプのインスタンスとして使用することができることを

は、私は、インスタンスがFractional型を持つ場合、それはタイプNumclass Num a => Fractional a where)を持っている必要があることを知っているが、それは必要なのでしょうか?


@mnoronhaご返信ありがとうございました。 1つの質問だけが私を混乱させます。私はtype aが機能で使用できない理由を知っています(/)type atype Integerであり、type class Fractionalのインスタンスではありません(引数のタイプはinstance of Fractionalでなければなりません)。私が理解できないことは、型整数をNumのインスタンスになるa型に変換する場合でも、a型がFractionalのインスタンスになることを意味しないということです(分数型のクラスはNum型のクラスよりも制約が強いためので、aタイプは、Fractional type classで必要な機能を実装していない可能性があります)。 aタイプが条件Fractional type classが必要とするものに完全に適合しない場合、引数タイプをFractionalのインスタンスにするように、関数(/)でどのように使用できますか?母国語でない人には申し訳ありません。本当にありがとうございます。


私はタイプが唯一の親型クラスに合うならば、それはより制約型クラスを必要とする関数では使用できないことをテストしました。

{-# LANGUAGE OverloadedStrings #-} 
module Main where 

class ParentAPI a where 
    printPar :: int -> a -> String 

class (ParentAPI a) => SubAPI a where 
    printSub :: a -> String 

data ParentDT = ParentDT Int 
instance ParentAPI ParentDT where 
    printPar i p = "par" 


testF :: (SubAPI a) => a -> String 
testF a = printSub a 

main = do 
    let m = testF $ ParentDT 10000 
    return() 
==== 
test-typeclass.hs:19:11: error: 
• No instance for (SubAPI ParentDT) arising from a use of ‘testF’ 
• In the expression: testF $ ParentDT 10000 
    In an equation for ‘m’: m = testF $ ParentDT 10000 
    In the expression: 
    do { let m = testF $ ParentDT 10000; 
     return() } 

私は非常に明確に数値オーバーロードのあいまいさを説明するドキュメントを発見したと同じ混乱で他人を助けるかもしれません。

https://www.haskell.org/tutorial/numbers.html

答えて

4

まず、FractionalNumの両方がタイプが、型クラスではないことに注意してください。ドキュメントや他の場所でそれらの詳細を読むことができますが、基本的な考え方は型の振る舞いを定義することです。 Numは、最も包括的な数値型のクラスで、(+),negateなどのビヘイビア関数を定義しています。これは、ほぼすべての「数値型」に共通しています。 Fractionalは、「実数除算をサポートする分数を記述する、より拘束型のクラス」です。

Fractionalの型クラス定義を見ると、実際にはNumのサブクラスとして定義されています。これは、インスタンスFractionalを持ってするタイプaのために、それは最初の型クラスNumのメンバーである必要がありますされています

class Num a => Fractional a where 

はのはFractionalによって制約されているいくつかのタイプを考えてみましょう。Numのすべてのメンバーに共通する基本的な振る舞いを実装しています。しかし、複数の制約が指定されていない限り(例えば、(Num a, Ord a) => a)、関数div :: Integral a => a -> a -> a(整数除算)を使用すると、他の型のクラスからの振る舞いを実装することは期待できません。型クラスFractionalによって制約(例。1.2 :: Fractional t => t)は、我々は、エラーが発生した。型クラスは、私たちが行動を共有するタイプのために、より具体的かつ有用な関数を記述することができ、機能のお得な情報を持つ値の並べ替えを制限する。

今より一般的なtypeclass、Numを見てみましょう。タイプ変数aNum a => aによってのみ制約されている場合は、(ほとんど)基本的な動作を実装することがわかりますNum型のクラス定義ですが、もっと知るにはもっと文脈が必要です。これは実際にはどういう意味ですか?我々はFractionalクラス宣言から、Fractionalタイプクラスで定義された関数がNum型に適用されることを知っています。しかしながら、これらのNumタイプは、すべて可能なNumタイプのサブセットである。

このすべての重要性は、最終的には地面タイプ(型クラス制約が関数で最もよく見られる)と関係がある。 aはタイプを表し、Num a => aaがタイプクラスNumのインスタンスを含むタイプであることを示しています。 aは、のインスタンス(例:Int,Natural)を含むタイプの任意のにすることができます。したがって、汎用型Num a => aの値を与えると、型クラスが定義されているすべての型に対して関数を実装できることがわかります。

ghci>> let a = 3 :: (Num a => a) 
ghci>> a/2 
1.5  

我々は、特定のタイプとして、あるいはより制約型クラスの面でaを定義したい場合、我々は同じ結果を期待することができていないでしょうに対し:たとえば

ghci>> let a = 3 :: Integral a => a 
ghci>> a/2 
-- Error: ambiguous type variable 

または

ghci>> let a = 3 :: Integer 
ghci>> a/2 
-- Error: No instance for (Fractional Integer) arising from a use of ‘/’ 

(編集質問をフォローする応答)

これは間違いなく最も具体的な説明ではないので、読者は自由にもっと厳しいものを提案することができます。

ghci>> :t (a 3) 
(a 3) :: Num a => a 
ghci>> :t (a 3.2) 
(a 3.2) :: Fractional a => a 

ながら:

a :: Num a => a -> a 
a = id 

のは、機能のいくつかのアプリケーションのための型シグネチャを見てみましょう:

と仮定我々はid機能の単なる型クラス制約のバージョンで機能aを持っています私たちの関数は一般的な型のシグネチャを持っていました。アプリケーションの結果、アプリケーションの型はより制限されていました。

ここで、関数fromIntegral :: (Num b, Integral a) => a -> bを見てみましょう。戻り値の型は一般的にNum bであり、これは入力に関係なく真となります。私はこの違いを考えてみる最良の方法は精度の点であると思います。fromIntegralは、より制約の厳しい型を取って制約を少なくしているため、結果が型のクラスから署名の制約を受けることが常に予想されます。しかし、入力制約を与えると、実際の入力は制約より制限され、結果の型はそれを反映します。

+0

Num a => aで制約されているが、Fractional型のクラスに制約されていない型変数 'a'。変数 'a'は'(/) 'のようないくつかの振る舞いを実装していないかもしれませんか?たとえば、分数動作が '(/)'を実装していない場合、 '(/)'関数で 'a'をどのように使うことができますか? – hliu

+0

'Fractional a => a 'で制約された型変数は、' Num'クラスで定義されたすべての動作を実装できます。 'Fractional a => a'をより厳格な制約として考えることができるので、同じことは逆には当てはまりません - 許容可能な' a'型は部分集合です。 – mnoronha

+0

詳細な返信をありがとうございます。質問は1つだけですが、ここではあまりにも長く、生の質問の後に投稿します。 – hliu

0

この理由が働く理由は、普遍的な定量化の仕方になります。私は型シグネチャ(あなたが-XExplicitForAllやその他のforall関連の拡張子を有効にした場合、あなたが自分で行うことができます)への明示的なforallに追加しようと思ってこれを説明するために、しかし、あなただけ(forall a. ...がちょうど...なり)、それらを削除した場合、すべてが動作します良い。

覚えておいていただきたいのは、関数が型クラスに制約された型を含む場合、それはその型クラス内でANY型を入出力できるということですから、実際には制約の少ない型クラスを持つ方が良いということです。

ので:

fromInteger :: forall a. Num a => Integer -> a 

fromInteger 5 :: forall a. Num a => a 

あなたはEVERY Num型である値を有することを意味します。したがって、Fractionalの関数で使用するだけでなく、NumMyWeirdTypeclassの両方を実装する単一の型がある限り、MyWeirdTypeclass a => ...の関数で使用できます。したがって、なぜあなたは得ることができるだけで罰金、以下:今もちろん、あなたが2で割るすることを決定したら、それは今で出力タイプを望んでいる

fromInteger 5/2 :: forall a. Fractional a => a 

Fractional、ひいては 5をすると 2はいくつか Fractional型として解釈されますしたがって、我々は Intの値を分割しようとする問題にぶつからず、上記のタイプのチェックを試みると、タイプチェックが失敗します。 Int

これは本当に強力で素晴らしいですが、他の言語ではこれをサポートしていないか、入力引数としてのみサポートしています(ほとんどの言語でprintは任意の印刷可能なタイプを取ります)。今

全体のスーパークラス/サブクラスのものが場に出たとき、あなたは好奇心旺盛であってもよいし、そうあなたがタイプNum a => aの何かに取る関数を定義する際に、そのユーザーがどのNumタイプに渡すことができるので、あなたは正しいですこのような状況で、あなたは、*のように、ALL Num値に取り組むだけで物事をNumのいくつかのサブクラスで定義された関数を使用することはできません。

double :: forall a. Num a => a -> a 
double n = n * 2 -- in here `n` really has type `exists a. Num a => a` 

したがって、次のチェックを入力しないと、それはいずれにもチェック入力しないでしょう引数がFractionalであることがわからないためです。

halve :: Num a => a -> a 
halve n = n/2 -- in here `n` really has type `exists a. Num a => a` 

我々はfromInteger 5/2は、以下、上位関数により同等であると上まで持って何を、括弧内のforallが必要であることに注意してください、あなたは-XRankNTypes使用する必要があります。このので

halve :: forall b. Fractional b => (forall a. Num a => a) -> b 
halve n = n/2 -- in here `n` has type `forall a. Num a => a` 

をあなたが以前に扱っていたfromInteger 5と同じように、すべてNumタイプを取り入れている時間は、Numタイプではありません。今、この機能(と誰がそれを望んでいない理由の一つ)の欠点は、あなたが本当にEVERY Numタイプの何かに合格しなければならないということです。

halve (2 :: Int) -- does not work 

halve (3 :: Integer) -- does not work 

halve (1 :: Double) -- does not work 

halve (4 :: Num a => a) -- works! 

halve (fromInteger 5) -- also works! 

私はそれは少し物事をクリア願っています。 fromInteger 5/2が機能するために必要なことは、NumFractionalのいずれか1つのタイプ、つまりFractionalNumを意味するので、ちょうどFractionalの1つのタイプが存在することです。 GHCがちょうどDoubleを選んでいて、Fractionalを選ぶことができたということを、あなたが気づいていないかもしれないので、タイプのデフォルト設定は、この混乱を取り除くことにはあまり役立ちません。

関連する問題