在ghci中:
:t (*)
(*) :: Num a => a -> a -> a
:t (/)
(/) :: Fractional a => a -> a -> a
為什么除法需要分數輸入?我的意思是,我理解“為什么”(因為它是這樣寫的),但我不明白為什么它是這樣實作的?*可以取并回傳小數,為什么不能/ ?我知道div并且quot存在,但我不明白為什么/不轉換它的引數*似乎(或者為什么它不會根據給定的引數成為 div/quot 的別名。
我確定是有原因的,我就是不知道它是什么?
uj5u.com熱心網友回復:
我認為您可能對約束的含義感到困惑。這些操作中的任何一個都不會進行自動轉換。約束本質上是一組支持此操作的型別。 Num是最基本的數字類,它的成員包括Integer,Double,Rational,等等,這意味著(*)可以具有任何以下型別的:
(*) :: Integer -> Integer -> Integer
(*) :: Double -> Double -> Double
(*) :: Rational -> Rational -> Rational
請特別注意,所有三種型別都是相同的——你必須放入兩個相同型別的,并且你總是得到你放入的東西。
你不能將兩個整數相除并得到一個整數,所以我們不能允許
(/) :: Integer -> Integer -> Integer
這就是為什么Fractional在除法下關閉的數字型別有更具體的類。
正如您所建議的那樣,可以在整數情況下(/)為 for做一個別名div。事實上,這是可能的——你可以在你的源檔案中寫下這個:
instance Fractional Integer where
(/) = div
fromRational = floor
現在Integers支持(/)整數除法運算子,就像其他語言一樣!我強烈反對這一點,我認為 Haskell 語言設計者做對了這一點。拋開允許您0.5在整數背景關系中撰寫文字并將其靜默解釋為零而不是錯誤(令人擔憂的語言數量允許!)的完全瘋狂,整數除法是與除法非常不同的操作。除法是乘法的倒數,它有這些很好的規律(盡管有 IEEE 舍入誤差):
(a / b) * b = a
(a b) / c = (a / c) (b / c)
整數除法不是也不是。所以如果(/)支持整數,那么如果你有一些多型算術函式:
someFormula x y = x / (x^2 y^2) y / (x^2 y^2)
你就像,嘿,這不必要地復雜,讓我簡化它!
someFormula x y = (x y) / (x^2 y^2)
您剛剛為決定使用someFormula整數的那個用戶引入了一個錯誤,它從來沒有打算用于它,因為在這種情況下,除法不是“真正的”除法(沒有雙關語),并且您的代數是錯誤的。所以這就是為什么我們必須Fractional指出這是一個誠實的型別支持除法,也就是說,乘法的逆。
uj5u.com熱心網友回復:
(*) :: Num a => a -> a -> a意味著您可以選擇a您喜歡的任何型別作為 的成員Num,并且*可以是一個函式,該函式接受該型別的兩個引數并回傳相同型別的結果。
型別類的作業方式,對于Num作為單獨實作的成員的每個型別,*必須提供。因此,有一個函式是(*) :: Integer -> Integer -> Integer,另一個是Double -> Double -> Double,另一個是Ratio Word32 -> Ratio Word32 -> Ratio Word32,等等,*可以實體化為任何這些單獨的函式。
同樣,(/) :: Fractional a => a -> a -> a意味著您可以選擇任何 type a,但在這里它必須是Fractionaltype 類的成員,這更具限制性。Double是 的成員Fractional,所以有一個(/) :: Double -> Double -> Double函式。Integer只是不是 的成員Fractional,所以沒有(/) :: Integer -> Integer -> Integer函式。
Integer不屬于的原因是Fractional整數的數學除法根本不會產生整數。您需要一種支持整數之間的小數的型別,以便除法更有意義。有不同的類似操作div,并quot可以對整數執行,所以他們對工種喜歡Integer和Int。如果你想要這些操作之一,你應該要求它,而不是要求/并期望它在你對整數使用它時給你一個不同的操作。尤其是因為它如何知道您認為“我可以用整數做的最接近除法的事情”是否div或是quot什么?如果你有一個Int,Integer,或類似的,并且您想將其轉換為小數,以便您可以對其進行除法并獲得小數結果,然后您需要使用諸如fromIntegral; Haskell 不會神奇地為你插入這個,但它有所有的翻譯功能,而且它們作業得很好。
請注意,呼叫時不會進行轉換*;以不*喜歡的方式轉換論點并不是很高興/。的Int版本* 要求你給它提供兩個Int引數,Double版本要求你提供兩個Double引數,等等。如果你呼叫Double版本*但傳遞給它 an Int,它不會將 the 轉換Int為 a Double:你會得到一個型別錯誤.
新 Haskellers 經常認為必須有轉換,因為他們可以評估像 的運算式3.2 * 7,還有什么可以作業的?
在其他幾種流行的語言中,這里發生的情況是語言實作說這3.2是一個浮點數并且7是一個整數,并且 - 就像在 Haskell 中一樣 - 乘法只能用兩個相同型別的引數來計算。所以語言實作默默地插入了一些代碼來將整數轉換為浮點型別;你得到的實際上類似于3.2 * (floatFromInt 7). 但是僅僅因為這是其他語言處理簡單算術運算式的方式并不能使它成為唯一的方法,當您開始學習Haskell(或您開始學習的任何其他語言)實際上如何處理這些運算式時,您會做得很好語言,而不是假設它必須像任何其他語言一樣。
在 Haskell 中,編譯器不會首先假設數字文字像3.2或7任何特定型別。Haskell 更靈活。相反,源代碼中的任何類似整數的數字都可以被讀取為任何數字型別( 中的任何型別Num)的文字,而源代碼中的小數點數字可以被讀取為 中的任何型別的文字Fractional。Haskell*通過查看它使用的背景關系(使用結果的代碼)以及引數來確定正在呼叫的版本。如果這些事情中的任何一個強制單一型別,那么引數和結果都必須能夠是單一型別。
因此,在這種情況下3.2 * 7,結果的使用方式可能會決定結果必須是什么型別,進而決定*該運算式中的型別,進而決定每個數字文字的讀取方式。兩者都將作為相同型別1 的文字讀入。在這種情況下,我們甚至可以確定7絕對不是 Int,或Integer,或任何類似的東西,因為這些型別沒有Fractional實體,并且使用的型別必須是Fractional一個,以便其中一個引數使用3.2包含小數點的文字。
1 . 當然,“作為任何數字型別的文字讀入”實際上意味著整數文字使 Haskell 創建一個Integer,然后fromInteger :: Num a => Integer -> a來自適當型別Num實體的函式可以從Integer. 類似地,小數點文字最初被讀取為 type 的值,Rational然后該fromRational :: Fractional a => Rational -> a函式用于構建實際值。
所以,如果你是非常挑剔和/或技術,你可以爭辯說,3.2其實總是一個字面的固定式Rational,然后轉換功能fromRational被一直使用。這并沒有錯,但我將其視為“3.2是任何Fractional型別的文字”概念的實作策略。無論如何,函式的應用程式幾乎總是在編譯時被行內,因此在運行時將只有最終所需型別的值。
我認為這個觀點對新的 Haskellers 更有幫助,正是因為很多人感到困惑,期望整數和浮點型別之間的自動型別轉換會像在其他語言中那樣發生,而事實絕對不是這樣;在那些其他語言中也會發生整數型別的變數。Haskell 的這種形式的轉換只與文字有關,它非常不同,因為在使用文字時總是呼叫fromRational(or fromInteger) 翻譯器,它與提升一種型別的真實運行時值以匹配另一個運算元的型別。
uj5u.com熱心網友回復:
在這些型別中:
:t (*)
(*) :: Num a => a -> a -> a
:t (/)
(/) :: Fractional a => a -> a -> a
有兩個部分:
- 約束 :Num, Fractional
- 型別:一個
該a部分表示您想要的“任何型別”(受約束),但它在每個地方都是相同的型別,因為它a在每個地方都有。另外,請注意沒有發生轉換,正是因為型別無處不a在。在Num與Fractional部分是型別層次結構的“超”:

所以,Num a意味著實數,或復數,或積分,或整數,或整數,或小數,或浮點數,或雙,或有理數。
問題是數學積分除法與分數除法不同。因此,Haskell 有 div 用于整數除法,和 / 用于分數除法。
:t div
div :: Integral a => a -> a -> a
:t (/)
(/) :: Fractional a => a -> a -> a
后果之一是您必須非常仔細地考慮您的演算法。以計算串列中值的平均值為例:
average xs = (sum xs) / length (xs)
簡單,但錯了。這不會編譯,因為型別是錯誤的:
:t sum
sum :: (Foldable t, Num a) => t a -> a
:t length
length :: Foldable t => t a -> Int
因此,您必須使用 Data.List 的 genericLength。
:t genericLength
genericLength :: Num i => [a] -> i
average :: Fractional a => [a] -> a
average xs = (sum xs) / genericLength (xs)
uj5u.com熱心網友回復:
簡而言之,型別類粗略地表示各種代數結構。
Num可以表示環的概念,一組值,其中加法和乘法行為“正常”但不需要除法。Fractional可以表示一個域的概念,它是一個(可交換的)環,增加了可分性的概念。Integral可以表示歐幾里德域的概念,它是一個(交換)環,具有稱為歐幾里德除法的寬松除法定義。
由于可以將受約束的函式視為函式族,因此您可以“專門化”(*) :: Num a => a -> a -> a來表示較小的族或特定函式,例如
(*) :: Int -> Int -> Int(*) :: Float -> Float -> Float(*) :: Fractional a => a -> a -> a.
每三個的是一種獨特的功能,第一和第二,因為Int和Float都是實體Num,第三因為Num是類的超類Fractional。
您可以將第三個視為(*)在(/) :: Fractional a => a -> a -> a族中具有逆函式的子集。其他函式沒有逆函式,但可以具有相關函式,div, mod :: Integral a => a -> a -> a以便
a * b == (a `div` b) * b (a `mod` b)
(FloatandDouble型別打破了類比,因為對它們的操作既不是封閉的,也不是關聯的。)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/404598.html
標籤:
