【发布时间】:2022-11-25 09:06:39
【问题描述】:
考虑以下模块:
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DefaultSignatures #-}
module Lib where
import Data.List (foldl')
doBigSum :: (Enum a, Num a) => a
doBigSum = foldl' (+) 0 [1..200000000]
f :: (Enum a, Num a) => a -> a
f x = x + doBigSum
class BigSum a where
bigSum :: a
default bigSum :: (Enum a, Num a) => a
bigSum = doBigSum
newtype A = A Integer deriving newtype (Enum, Num, Show)
newtype B = B Integer deriving newtype (Enum, Num, Show)
instance BigSum A where
bigSum = doBigSum
instance BigSum B
g :: (Num a, BigSum a) => a -> a
g x = x + bigSum
假设我们在这里也使用 GHC。
在这里我要注意一些事情(我相信这是真的,如果我错了请纠正我):
- 除非有一些花哨的优化/内联,否则
doBigSum很有可能不会被缓存,而是为每个引用重新计算,因为doBigSum实际上有一个隐藏的参数,它是类型a的类型类字典它正在被实例化。 - 然而,在实例定义中
BigSum A、bigSum将被缓存,并且每个后续引用都将使用该值。事实上,这就是我所看到的,如果我创建一个像这样的主要功能:
import Lib main :: IO () main = do print "Start" print ((f 1) :: A) print ((f 2) :: A)并且在没有优化的情况下编译(单独的模块在这里很重要),两个打印语句的输出之间显然存在时间差距。
但如果我这样做:
import Lib main :: IO () main = do print "Start" print ((g 1) :: A) print ((g 2) :: A)然后
g 2的结果紧跟在g 1的结果之后打印。显然,BigSum A的实例定义导致为bigSum :: A创建一个单独的常量。现在考虑
import Lib main :: IO () main = do print "Start" print ((g 1) :: B) print ((g 2) :: B)请注意,
BigSum B的实例定义并不明确,它取决于默认值。现在这里发生了什么?是吗:
-
bigSum的一个实现,即默认值,它有一个类型的隐藏参数,很像doBigSum做的,所以结果不被缓存或 - 对于
BigSum的每个实例都有一个单独的bigSum实现,它专用于所讨论的类型,因此当为特定类型调用bigSum时,它只会为该类型计算一次。我的测试表明发生的情况是案例 (2),这对我的用例很有用,但我想知道我可以在多大程度上依赖它。
我的实际用例更像是以下内容:
data ConversionInfo a = ... data Conversions a = Conversions { convA :: a -> A, convB :: a -> B, convC :: a -> C } f :: ConversionInfo a -> Conversions a f = ... -- Lots of work happens here class SimpleConversion a where conversionInfo :: ConversionInfo a conversions :: Conversions a conversions = f conversionInfo class Conversions a where conversionA :: a -> A default conversionA :: SimpleConversion a => a -> A conversionA = convA conversions conversionB :: a -> B default conversionB :: SimpleConversion a => a -> B conversionB = convB conversions conversionC :: a -> C default conversionC :: SimpleConversion a => a -> C conversionC = convC conversions我想可靠地确定的是,每次我为某些
X和blah调用conversionX blah时,都不会重新计算f。相反,我希望f对每种类型的SimpleConversion只运行一次。其他任何事情都会完全增加运行时成本,因为与实际转换相比,f做了很多工作。对此的任何文档/参考将不胜感激。
-
【问题讨论】: