2010-03-04 28 views
63

我正在玩初學者哈斯克爾,我想寫一個平均功能。這似乎是世界上最簡單的事情,對吧?Haskell類型令人沮喪的一個簡單的'平均'功能

錯誤。

看起來Haskell的類型系統禁止使用泛型數字類型的平均值 - 我可以讓它在Integrals列表或Fractionals列表上工作,但不能同時使用它們。

我想:

average :: (Num a, Fractional b) => [a] -> b 
average xs = ... 

但我只能得到:

averageInt :: (Integral a, Fractional b) => [a] -> b 
averageInt xs = fromIntegral (sum xs)/fromIntegral (length xs) 

averageFrac :: (Fractional a) => [a] -> a 
averageFrac xs = sum xs/fromIntegral (length xs) 

,第二個似乎工作。直到我嘗試傳遞一個變量。

*Main> averageFrac [1,2,3] 
2.0 
*Main> let x = [1,2,3] 
*Main> :t x 
x :: [Integer] 
*Main> averageFrac x 

<interactive>:1:0: 
    No instance for (Fractional Integer) 
     arising from a use of `averageFrac ' at <interactive>:1:0-8 
    Possible fix: add an instance declaration for (Fractional Integer) 
    In the expression: average x 
    In the definition of `it': it = averageFrac x 

顯然,Haskell對它的類型真的很挑剔。這就說得通了。但不是當他們都可以[數字]

我是否缺少一個明顯的RealFrac應用程序?

是否有辦法強制積分成小數部分,當它得到分數輸入時不會窒息?

是否有某種方法可以使用Eithereither來生成某種可以在任何類型的數值數組上工作的多態平均函數?

Haskell的類型系統是否完全禁止這個函數存在?

學習Haskell就像學習微積分。它非常複雜,基於理論的高峯,有時問題非常複雜,以至於我甚至不知道正確地提出問題,所以任何見解都會被熱烈接受。 (另外,腳註:這是基於一個家庭作業問題,每個人都同意averageFrac,上面得到滿分,但我有一個偷偷摸摸的懷疑,有一種方法可以使它在Integral和Fractional數組上工作)

+0

http:// stackoverflow。com/questions/1816993/haskell-dividing-num –

回答

87

所以根本上說,你被(/)類型的約束:

(/) :: (Fractional a) => a -> a -> a 

順便說一句,你還想Data.List.genericLength

genericLength :: (Num i) => [b] -> i 

所以怎麼樣去除fromIntegral更多的東西一般:

import Data.List 

average xs = realToFrac (sum xs)/genericLength xs 

它只有一個真正的約束(智力,整型,浮點型,雙)...

average :: (Real a, Fractional b) => [a] -> b 

這樣會採取任何真正進入任何分數。

並注意到所有海報都被Haskell中的多態數字文字所捕獲。 1不是一個整數,它是任何數字。

Real類只提供一種方法:將類Num中的值轉換爲理性的能力。這正是我們在這裏所需要的。

就這樣,

Prelude> average ([1 .. 10] :: [Double]) 
5.5 
Prelude> average ([1 .. 10] :: [Int]) 
5.5 
Prelude> average ([1 .. 10] :: [Float]) 
5.5 
Prelude> average ([1 .. 10] :: [Data.Word.Word8]) 
5.5 
+0

但是如果我想在一個雙倍列表上調用平均值呢? 是否有一個類似numToFrac的函數,它使用Real或Fractional並返回小數? 我們可以寫一個嗎? – jakebman

+5

你可以給它一個雙打列表,因爲Double是真實的。 「average([1..10] :: [Double])」。 Real類增加了精確的能力,從Num中的事物中構造一個合理的價值。這正是你需要的。 –

+0

你是對的!感謝澄清! 在數字下有哪些類型的realToFrac不起作用?我看不出爲什麼它不是numToFrac。 – jakebman

-6

呀,Haskell的類型系統是很挑剔的。這裏的問題是fromIntegral類型:

Prelude> :t fromIntegral 
fromIntegral :: (Integral a, Num b) => a -> b 

fromIntegral將接受積分作爲,沒有任何其他類型民的。 (/),另一方面只接受小數。你如何使這兩個工作在一起?

那麼,SUM函數是一個良好的開端:

Prelude> :t sum 
sum :: (Num a) => [a] -> a 

琛接受任何民的列表,並返回一個民。

您的下一個問題是列表的長度。長度爲Int:

Prelude> :t length 
length :: [a] -> Int 

您還需要將該Int轉換爲Num。這就是整體所做的。

所以現在你已經有了一個函數返回一個Num和另一個返回Num的函數。有類型推廣數字you can look up的一些規則,但基本上在這一點上,你是好去:

Prelude> let average xs = (sum xs)/(fromIntegral (length xs)) 
Prelude> :t average 
average :: (Fractional a) => [a] -> a 

讓我們給它一個試運行:

Prelude> average [1,2,3,4,5] 
3.0 
Prelude> average [1.2,3.4,5.6,7.8,9.0] 
5.4 
Prelude> average [1.2,3,4.5,6,7.8,9] 
5.25 
+11

你也跟Michael一樣陷入了同樣的陷阱。數字重載! 5是*不是整數值。它是* any * Num。這裏它默認爲一個小數值 - 你不能傳入Int或Integer。 - 因爲你會得到沒有實例(分數詮釋) –

+0

是的,我的壞在那裏。我沒有給予足夠的關注。我想這正是我爲什麼從未在Haskell中做過比玩具編程更多的原因。即使作爲一種束縛與紀律的語言的粉絲,我發現Haskell對主人有點殘酷。 –

+0

Haskell不是B&D。像B&D一樣接近它將會很痛苦。如果你想學習Haskell,你需要掌握類型系統。這裏的所有都是它的。當你知道如何爲你使用**類型時,你的困惑就會消失。 – nomen

21

的問題一直非常好Dons回答說,我想我可能會補充一些東西。

在計算平均值是這樣的:

average xs = realToFrac (sum xs)/genericLength xs

你的代碼將做的是兩次遍歷列表,一旦計算出其元素的總和,而一旦得到它的長度。 據我所知,GHC還沒有能夠優化這一點,並且一次性計算總和和長度。

即使作爲一個初學者思考它和可能的解決方案,它也不會受到傷害,例如可以使用計算總和和長度的摺疊來編寫平均函數;上ghci:

:set -XBangPatterns 

import Data.List 

let avg l=let (t,n) = foldl' (\(!b,!c) a -> (a+b,c+1)) (0,0) l in realToFrac(t)/realToFrac(n) 

avg ([1,2,3,4]::[Int]) 
2.5 
avg ([1,2,3,4]::[Double]) 
2.5 

該功能看起來不夠優雅,但性能更好。

上笠博客

的更多信息:

http://donsbot.wordpress.com/2008/06/04/haskell-as-fast-as-c-working-at-a-high-altitude-for-low-level-performance/

+3

+1表示摺疊以獲得更好的性能提升,但我只會建議您在完全理解Haskell後嘗試性能,並且整數多態性是您孩子的遊戲。 –

+8

對Robert Massaioli評論+1,因爲這個avg實際上是非常糟糕的... foldl'在累加器中是嚴格的,對於Haskell來說這基本上意味着「弱頭標準形式」,嚴格限制到第一個數據構造器。因此,例如在這裏,foldl'將保證累加器將被評估爲足以確定它是一對(第一個數據構造函數),但內容將不會被評估,因此累計thunk。在這種情況下,使用'foldl'(\(!b,!c)a - > ...或者一個嚴格的pair類型數據P a = P!a!a'是獲得良好性能的關鍵。 – Jedai

+0

+1傑代的評論,我相應地編輯了這篇文章。 –

7

由於行蹤詭祕曾在回答你的問題做了這樣一個好工作,我會在質疑你的問題的工作....

例如,在你的問題中,你首先在給定清單上運行平均值,得到一個很好的答案。然後,你看起來像是完全相同的列表,將其分配給一個變量,然後使用函數變量......然後發生變化。

您已經運行到什麼這裏是一個建立在編譯器,稱爲DMR:在d readed 中號 onomorphic [R estriction。當你直接將該列表傳遞給函數時,編譯器不會假定數字是哪種類型,它只是根據用法推斷出它可能的類型,然後在不能再縮小字段的情況下選擇它。這有點像鴨子打字那樣。

無論如何,當您將列表分配給一個變量時,DMR會被踢入。由於您已將列表放入一個變量中,但沒有提示如何使用它,DMR讓編譯器選擇了一個類型,在這種情況下,它選擇了一個與表單匹配的並且似乎適合的表單:Integer。由於您的函數在其/操作中無法使用整數(它需要Fractional類中的類型),因此非常抱怨:Fractional類中沒有Integer實例。有些選項可以在GHC中設置,這樣它就不會強制你的值變成一個單一的形式(「單一形態」,得到它?),直到它需要,但它使任何錯誤信息稍微難以弄清楚。現在

,在另一方面,你必須穿上答案的回覆,引起了我的注意:

我cs.ut.ee/~varmo/MFP2004的最後一頁上誤導由圖/PreludeTour.pdf ,它顯示浮動NOT從Real繼承屬性,然後我假定它們將不共享任何類型。

Haskell的類型與您習慣的類型不同。 RealFloating是類型類,它比對象類更像接口。他們告訴你可以用該類中的類型做什麼,但這並不意味着某種類型不能做其他事情,只不過有一個接口意味着一個(n OO風格)類不能有其他人。

學習Haskell就像學習微積分

我要說學習Haskell就像學習瑞典語 - 有很多小的,簡單的事情(字母,數字),其外觀和工作方式相同,但還有一些詞語看起來應該是一件事,當它們實際上意味着別的東西時。但是一旦你流利地使用它,你的普通朋友會驚訝於你如何能夠吹噓這種古怪的東西,使華麗的美女做出驚人的技巧。奇怪的是,從一開始就有很多人蔘與Haskell,他們也知道瑞典語。也許這個比喻不僅僅是一個比喻...

2
:m Data.List 
let list = [1..10] 
let average = div (sum list) (genericLength list) 
average 
相關問題