通常最好是对函数使用最严格或最宽松的类型定义吗?每种方法的优缺点是什么?我发现当我用严格的双打重写my pearson correlation code时,我更容易写,跟随和推理(这可能只是缺乏经验)。但我也可以看到,更广泛的类型定义如何使函数更普遍适用。是否将更严格的类型定义描述为一种技术债务?
使用Typeclasses:
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)
答案 0 :(得分:8)
可读性是一个意见问题。一般来说,我发现更通用的类型签名更具可读性,因为可能的定义较少(有时甚至只有一个非分歧定义)。例如,看到mean
只有Fractional
约束会立即限制在该函数中执行的操作(与可能正在执行Double
操作的sqrt
版本相比我知道)。当然,概括类型is not always more readable。 (And just for fun)
拥有更多通用版本的函数的主要缺点是它们可能在运行时保持未经优化,因此Double
函数的Floating
字典必须传递给{{1每次调用它。
通过添加SPECIALIZE
pragma,您可以拥有最好的世界。这告诉编译器基本上复制了一些实例化的类型变量的函数代码。如果您知道仅使用mean
来调用mean
函数,那么这就是我要做的事情
Double
您也可以在代码中看到签名的专用版本!耶!