2017-02-21 188 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