'Share' 或 'cache' 仅由不明确类型参数化的表达式?
'Share' or 'cache' an expression parameterized by only ambiguous types?
我有一个棘手的问题;
所以,我知道 GHC 会“缓存”(因为缺少更好的术语)顶级定义并且只计算一次,例如:
myList :: [Int]
myList = fmap (*10) [0..10]
即使我在几个地方使用 myList
,GHC 也会注意到该值没有参数,因此它可以共享它并且不会“重建”列表。
我想这样做,但是计算依赖于类型级别的上下文;一个简化的例子是:
dependentList :: forall n. (KnownNat n) => [Nat]
dependentList = [0..natVal (Proxy @n)]
所以这里有趣的是,dependentList
没有一个“单一的”可缓存值;但是一旦应用了一个类型,它就会减少到一个常数,所以理论上一旦类型检查器 运行s,GHC 就可以识别出几个点都依赖于“相同的”dependentList
;例如(使用 TypeApplications)
main = do
print (dependentList @5)
print (dependentList @10)
print (dependentList @5)
我的问题是,GHC 会认识到它可以共享两个 5
列表吗?还是单独计算每一个?从技术上讲,甚至可以在编译时而不是 运行 时计算这些值,是否有可能让 GHC 这样做?
我的情况有点复杂,但应该遵循与示例相同的约束,但是我的 dependentList
类值需要大量计算。
如果可能的话,我一点也不反对使用类型类来做这件事; GHC 缓存和重用类型类字典吗?也许我可以将它烘焙到类型类字典中的常量中以获得缓存?
有人有想法吗?或者有人给我读过它是如何工作的吗?
我更愿意以编译器可以解决的方式来执行此操作,而不是使用手动记忆,但我对想法持开放态度:)
感谢您的宝贵时间!
根据@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
很明显,它每次使用时都会重新计算多态常量。
但是如果我们将常量写入一个类型class(仍然使用模糊类型),它似乎每个实例只会解析一次字典值,当你知道 GHC 通过相同的时,这是有意义的dict 用于相同的 class 个实例。它当然会重新 运行 不同实例的代码:
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 在编译时执行这些操作而言,看起来您可以使用 .
使用 TemplateHaskell 中的 lift
来执行此操作
不幸的是你不能在 typeclass 定义中使用它,因为 TH 会抱怨 '@n' 必须从不同的模块导入(yay TH)并且在编译时具体未知.您可以在任何使用 typeclass 值的地方执行此操作,但它会在每次提升时对其进行一次评估,并且您必须在使用它的任何地方提升才能获得好处;很不切实际。
我有一个棘手的问题;
所以,我知道 GHC 会“缓存”(因为缺少更好的术语)顶级定义并且只计算一次,例如:
myList :: [Int]
myList = fmap (*10) [0..10]
即使我在几个地方使用 myList
,GHC 也会注意到该值没有参数,因此它可以共享它并且不会“重建”列表。
我想这样做,但是计算依赖于类型级别的上下文;一个简化的例子是:
dependentList :: forall n. (KnownNat n) => [Nat]
dependentList = [0..natVal (Proxy @n)]
所以这里有趣的是,dependentList
没有一个“单一的”可缓存值;但是一旦应用了一个类型,它就会减少到一个常数,所以理论上一旦类型检查器 运行s,GHC 就可以识别出几个点都依赖于“相同的”dependentList
;例如(使用 TypeApplications)
main = do
print (dependentList @5)
print (dependentList @10)
print (dependentList @5)
我的问题是,GHC 会认识到它可以共享两个 5
列表吗?还是单独计算每一个?从技术上讲,甚至可以在编译时而不是 运行 时计算这些值,是否有可能让 GHC 这样做?
我的情况有点复杂,但应该遵循与示例相同的约束,但是我的 dependentList
类值需要大量计算。
如果可能的话,我一点也不反对使用类型类来做这件事; GHC 缓存和重用类型类字典吗?也许我可以将它烘焙到类型类字典中的常量中以获得缓存?
有人有想法吗?或者有人给我读过它是如何工作的吗?
我更愿意以编译器可以解决的方式来执行此操作,而不是使用手动记忆,但我对想法持开放态度:)
感谢您的宝贵时间!
根据@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
很明显,它每次使用时都会重新计算多态常量。
但是如果我们将常量写入一个类型class(仍然使用模糊类型),它似乎每个实例只会解析一次字典值,当你知道 GHC 通过相同的时,这是有意义的dict 用于相同的 class 个实例。它当然会重新 运行 不同实例的代码:
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 在编译时执行这些操作而言,看起来您可以使用
lift
来执行此操作
不幸的是你不能在 typeclass 定义中使用它,因为 TH 会抱怨 '@n' 必须从不同的模块导入(yay TH)并且在编译时具体未知.您可以在任何使用 typeclass 值的地方执行此操作,但它会在每次提升时对其进行一次评估,并且您必须在使用它的任何地方提升才能获得好处;很不切实际。