2016-11-12 50 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功能的整數類型轉換爲NUM(fromInteger具有類型fromInteger :: Num a => Integer -> a),但我不理解如何可以在類型Num被轉換隱含地到Fractional

我知道,如果一個實例的類型是Fractional必須有類型Numclass Num a => Fractional a where),但確實有必要,如果一個實例的類型是Num它可以被用作一個實例與Fractional類型?


@mnoronha感謝您的詳細回覆。只有一個問題讓我困惑。我知道type a不能在功能(/)中使用的原因是type atype Integer不是type class Fractional(功能(/)要求參數類型必須是instance of Fractional)的實例。我不明白的是,即使通過調用fromInteger將類型整數轉換爲a類型(它是Num的一個實例),它並不意味着a類型是Fractional的一個實例(因爲分數類型類比Num類型類更受約束,因此a類型可能無法實現Fractional type class所要求的某些功能)。如果a類型不完全符合Fractional type class要求的條件,那麼如何在功能(/)中使用,該函數會將參數類型作爲分數的實例。對不是母語的人抱歉,非常感謝您的耐心等待!


我測試過如果一個類型只適合父類型類,它不能用在需要更多約束類型類的函數中。

{-# 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是最具包容性的數字類型類,定義了幾乎所有「數字類型」常見的行爲函數,如(+),negateFractional是一個更受限制的類,它描述了「分數,支持真正的分割」。

如果我們查看Fractional的類型類定義,我們看到它實際上定義爲Num的子類。也就是說,對於一個類型a是一個有一個實例Fractional,它必須首先是類型類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),我們遇到了一個錯誤。類型類限制排序值的功能涉及的,使我們能夠寫出更加具體和實用的功能爲共享的行爲類型。

現在讓我們看看更一般的類型類型,Num。如果我們有一個類型變量a,它只受到Num a => a的約束,我們知道它將實現包含(少數)基本行爲在Num類型的類定義中,但我們需要更多的上下文來了解更多。這實際上意味着什麼?我們從Fractional類聲明知道Fractional類型類中定義的函數適用於Num類型。然而,這些類型是所有可能的Num類型的子集

所有這一切的重要性最終都與地面類型(其中函數中最常見的類型約束)有關。 a代表一種類型,符號Num a => a告訴我們a是一種包含類型爲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 ‘/’ 

(編輯響應的後續問題)

這絕對不是最具體的解釋,因此讀者可以自由地提出更嚴謹的建議。

假設我們有一個功能a這僅僅是id函數的類型類受限版本:

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

讓我們來看看類型簽名功能的一些應用:

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

雖然我們的函數具有通用類型簽名,由於其應用程序的應用程序類型更受限制。

現在,讓我們看看功能fromIntegral :: (Num b, Integral a) => a -> b。在這裏,返回類型是通用的Num b,並且無論輸入如何都是如此。我認爲考慮這種差異的最好方法是精確度。fromIntegral需要一個更受約束的類型,並且使它更少約束,所以我們知道我們總是期望結果將受到簽名中類型類的約束。但是,如果我們給出一個輸入約束,實際輸入可能會比約束更受限制,並且結果類型會反映這一點。

+0

如果一個類型變量'a'只受限於Num a => a,但不受Fractional類型的約束。那是說變量'a'可能不會實現一些行爲函數,比如'(/)'?如果例如分數行爲函數'(/)'沒有實現,那麼'(/)'函數中可以如何使用'a'? – hliu

+0

我們知道由'Fractional a => a'約束的類型變量將能夠實現'Num'類中定義的所有行爲。反過來也是如此,因爲我們可以將'Fractional a => a'視爲更嚴格的約束 - >可接受的'a'類型是一個子集。 – mnoronha

+0

感謝您的詳細回覆。這裏只有一個問題,但太長了,我在原始問題後發帖。 – hliu

0

這項工作的原因歸結爲普遍量化的工作方式。爲了幫助解釋這個問題,我將添加明確的forall到類型簽名(如果您啓用-XExplicitForAll或任何其他forall相關的擴展名,您可以自己做),但是如果您剛刪除它們(forall a. ...就變成...)精細。

需要記住的是,當一個函數涉及一個受類型類型約束的類型時,那麼這意味着您可以在該類型類型中輸入/輸出ANY類型,所以實際上具有較少約束的類型類型會更好。

所以:

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

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

,你有一個值,該值是每一個Num型的手段。因此,您不僅可以將它用於函數Fractional中,只要存在一個同時實現NumMyWeirdTypeclass的單一類型,就可以在僅使用MyWeirdTypeclass a => ...的函數中使用它。因此,爲什麼你可以得到以下就好:

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

現在當然一旦你決定除以2,它現在希望的輸出類型爲Fractional,從而52將被解釋爲一些Fractional類型,所以我們不會遇到我們試圖將Int值分開的問題,因爲試圖使上面的類型爲Int將無法​​進行類型檢查。

這真的很強大,很棒但很陌生,因爲一般其他語言不支持這一點,或只支持輸入參數(例如大多數語言中的print可以採用任何可打印類型)。

現在你可能會好奇,當整個超/子類的東西進場,所以當你要定義一個函數,它在Num a => a類型的東西,那麼因爲用戶可以在任何Num型傳輸,你是正確的,在這種情況下,你不能使用上的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` 

由於這你需要花費每種類型的Num類型(就像之前處理的fromInteger 5一樣),而不僅僅是任何Num類型。現在,這個功能(以及爲什麼沒有人希望它的原因之一)的缺點是,你確實在每一個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所需的全部工作是存在一種單一類型,既可以是Num也可以是Fractional,或換句話說就是Fractional,因爲Fractional意味着Num。類型違約對清除這種混淆並沒有多大幫助,因爲您可能沒有意識到,GHC只是隨意挑選Double,它可能會選擇任何Fractional

相關問題