2017-02-21 83 views
3

一般情況下,最好是對某個函數使用最嚴格或最寬鬆的類型定義嗎?每種方法的優缺點是什麼?我發現當我使用嚴格雙打重寫my pearson correlation code時,我更容易寫出,跟隨和推理(這可能只是缺乏經驗)。但是我也可以看到,如果有更廣泛的類型定義會使這些功能更普遍適用。更嚴格的類型定義是否被視爲技術債務的一種形式?功能類型限制

隨着類型類:

import Data.List 

mean :: Fractional a => [a] -> a 
mean xs = s/n 
    where 
     (s , n) = foldl' k (0,0) xs 
     k (s, n) x = s `seq` n `seq` (s + x, n + 1) 

covariance :: Fractional a => [a] -> [a] -> a 
covariance xs ys = mean productXY 
    where 
    productXY = zipWith (*) [x - mx | x <- xs] [y - my | y <- ys] 
    mx  = mean xs 
    my  = mean ys 

stddev :: Floating a => [a] -> a 
stddev xs = sqrt (covariance xs xs) 

pearson :: RealFloat a => [a] -> [a] -> a 
pearson x y = fifthRound $ covariance x y/(stddev x * stddev y) 

pearsonMatrix :: RealFloat a => [[a]] -> [[a]] 
pearsonMatrix (x:xs) = [pearson x y | y <- x:xs]:(pearsonMatrix xs) 
pearsonMatrix [] = [] 

fifthRound :: RealFrac a => a -> a 
fifthRound x = (/100000) $ fromIntegral $ round (x * 100000) 

隨着雙打:

import Data.List 

mean :: [Double] -> Double 
mean xs = s/n 
    where 
     (s , n) = foldl' k (0,0) xs 
     k (s, n) x = s `seq` n `seq` (s + x, n + 1) 

covariance :: [Double] -> [Double] -> Double 
covariance xs ys = mean productXY 
    where 
    productXY = zipWith (*) [x - mx | x <- xs] [y - my | y <- ys] 
    mx  = mean xs 
    my  = mean ys 

stddev :: [Double] -> Double 
stddev xs = sqrt (covariance xs xs) 

pearson :: [Double] -> [Double] -> Double 
pearson x y = fifthRound (covariance x y/(stddev x * stddev y)) 

pearsonMatrix :: [[Double]] -> [[Double]] 
pearsonMatrix (x:xs) = [pearson x y | y <- x:xs]:(pearsonMatrix xs) 
pearsonMatrix [] = [] 

fifthRound :: Double -> Double 
fifthRound x = (/100000) $ fromIntegral $ round (x * 100000) 

回答

8

可讀性是見仁見智。一般來說,我發現更通用的類型簽名更具可讀性,因爲可能的定義更少(有時甚至只有一個非發散定義)。例如,看到mean僅具有Fractional約束立即限制在該函數中執行的操作(與可能對我所知的所有操作執行操作的Double版本相比)。當然,泛化類型is not alwaysmore readable。 (And just for fun

具有功能更一般的版本的主要缺點是,它們可能在運行時保持未優化,使得的Floating功能Double的字典具有每次調用時要傳遞給mean

您可以通過添加一個SPECIALIZE pragma來擁有最好的世界。這告訴編譯器基本上覆制你的函數代碼與一些實例化的類型變量。如果你知道你只用Double將被調用你mean功能相當多,那麼這就是我會做什麼

{-# SPECIALIZE mean :: [Double] -> Double #-} 
mean :: Fractional a => [a] -> a 
mean xs = s/n 
    where 
    (s , n) = foldl' k (0,0) xs 
    k (s, n) x = s `seq` n `seq` (s + x, n + 1) 

而且你能看到簽名的專用版本在你的代碼呢!好極了!

+1

沒錯,雖然有時候最普遍的多態簽名只是有點太瘋狂了。如果單獨的約束列表比完整的專用類型長兩倍,我會考慮它是否真的合理。 (雖然'ConstraintKind'''type'defs可以使這樣的簽名更具可讀性,但是這在錯誤信息等方面的代價是晦澀難懂的。) – leftaroundabout