我需要两个高阶函数,一个采用副作用自由函数,另一个采用monadic函数作为参数:
frobM :: Monad m => (a -> m a) -> b -> m b
frob :: (a -> a) -> b -> b
它们非常相似,第二个函数可以用第一个表示:
frob f = runIdentity . frobM (return . f)
我希望这与手写“frob
”同样有效,因为Identity
是一个应该在编译时删除的新类型。然而,无论如何,性能仍然受到影响,正如这一标准微基准所证明的那样:
import Criterion.Main (bench, defaultMain, nf)
import Data.Functor.Identity (Identity (runIdentity))
main :: IO ()
main = defaultMain
[ bench "frob" $ nf (frob (+23)) 42
, bench "frob'" $ nf (frob' (+23)) 42
]
-- | Generalized function
frobM :: Monad m => (Int -> m Int) -> Int -> m Int
frobM f n = f $ 2^n `mod` 1024
-- | Specialized function based on the general function
frob' :: (Int -> Int) -> Int -> Int
frob' f = runIdentity . frobM (return . f)
-- | "Hand written" specialized function.
frob :: (Int -> Int) -> Int -> Int
frob f n = f $ 2^n `mod` 1024
输出:
benchmarking frob
time 676.1 ns (673.5 ns .. 678.9 ns)
1.000 R² (1.000 R² .. 1.000 R²)
mean 675.7 ns (673.3 ns .. 677.3 ns)
std dev 6.600 ns (5.984 ns .. 7.324 ns)
benchmarking frob'
time 706.2 ns (705.0 ns .. 707.9 ns)
1.000 R² (1.000 R² .. 1.000 R²)
mean 705.7 ns (704.5 ns .. 707.4 ns)
std dev 4.630 ns (3.729 ns .. 6.226 ns)
造成这种情况的原因是什么?有没有办法让frob'
和frob
一样高效?