我有一个棘手的问题;
因此,我知道GHC将“缓存”(由于缺乏更好的术语)顶级定义,并且只计算一次,例如:
myList :: [Int]
myList = fmap (*10) [0..10]
即使我在多个地方使用myList
,GHC也会注意到该值没有参数,因此它可以共享它并且不会“重建”列表。
我想这样做,但是要根据类型级别的上下文进行计算;一个简化的示例是:
dependentList :: forall n. (KnownNat n) => [Nat]
dependentList = [0..natVal (Proxy @n)]
所以这里有趣的是,dependentList
没有“单个”可缓存值;但是一旦应用了类型,它就会降为常数,因此从理论上讲,一旦运行类型检查器,GHC就会认识到几个点都取决于“相同” dependentList
;例如(使用TypeApplications)
main = do
print (dependentList @5)
print (dependentList @10)
print (dependentList @5)
我的问题是,GHC会认识到它可以共享两个5
列表吗?还是单独计算每个?从技术上讲,甚至可以在编译时而不是运行时计算这些值,是否有可能让GHC做到这一点?
我的情况稍微复杂一点,但是应该遵循与示例相同的约束条件,但是我的类似dependentList
的值需要大量计算。
如果有可能,我完全不反对使用typeclass进行操作; GHC会缓存并重用typeclass词典吗?也许我可以将其烘焙为typeclass dict中的常量以进行缓存?
想法有人吗?还是有人在读我的书以了解其工作原理?
我希望这样做的方式是使编译器可以解决该问题,而不是使用手动记忆,但是我愿意接受想法:)
感谢您的时间!
答案 0 :(得分:4)
按照@crockeea的建议,我进行了一项实验;这是尝试使用带有多态歧义类型变量的顶级常量,也是一个出于娱乐目的的实际常量,每个常量都包含一个“ trace”
dependant :: forall n . KnownNat n => Natural
dependant = trace ("eval: " ++ show (natVal (Proxy @n))) (natVal (Proxy @n))
constantVal :: Natural
constantVal = trace "constant val: 1" 1
main :: IO ()
main = do
print (dependant @1)
print (dependant @1)
print constantVal
print constantVal
结果很不幸:
λ> main
eval: 1
1
eval: 1
1
constant val: 1
1
1
因此很明显,它每次使用时都会重新评估多态常数。
但是,如果我们将常量写入类型类(仍然使用歧义类型),则似乎每个实例只能解析一次Dictionary值,当您知道GHC对相同的类实例传递相同的dict时,这是有意义的。当然,它确实可以为不同的实例重新运行代码:
class DependantClass n where
classNat :: Natural
instance (KnownNat n) => DependantClass (n :: Nat) where
classNat = trace ("dependant class: " ++ show (natVal (Proxy @n))) (natVal (Proxy @n))
main :: IO ()
main = do
print (classNat @1)
print (classNat @1)
print (classNat @2)
结果:
λ> main
dependant class: 1
1
1
dependant class: 2
2
就让GHC在编译时执行这些操作而言,您似乎可以通过this technique使用TemplateHaskell的lift
来完成。
不幸的是,您不能在typeclass定义中使用它,因为TH会抱怨'@n'必须从另一个模块(也就是TH)中导入,并且在编译时无法具体知道。您可以在任何使用typeclass值的地方进行此操作,但是它将每次提升一次对其进行评估,因此您必须在任何地方提升它,以获取收益。太不切实际了。