多态"常数",如5 :: Num a => a
,不是真正的常量,而是字典参数的函数。因此,如果你定义
primes :: Num n => [n]
primes = ...
当然不好的例子,这里没有充分理由让它具有多态性......我真正感兴趣的是如果你试图全局记忆一个非平凡的多态函数,例如: memo-trie
第
那么这个序列不会在来自不同站点的呼叫之间共享,这在性能方面并不好。 (这不是Haskell标准以可怕的单态限制为我们祝福的主要原因吗?)
我能看到如何强制分享的唯一方法就是拥有一个单形的标签"围绕约束类的每个实例。 E.g。
erastothenes :: Num n => [n]
erastothenes = ...
class (Num n) => HasPrimes n where
-- | @'primes' ≡ 'erastothenes'@
primes :: [n]
integerPrimes :: [Integer]
integerPrimes = erastothenes
instance HasPrimes Integer where
primes = integerPrimes
......在优雅方面并不好。
有没有更好的方法来实现这样的备忘录?
答案 0 :(得分:9)
出于相当技术原因,这是不可能的。类型类是开放的,因此多态常量在编译时不能“看到”有多少类型满足约束,因此它不能分配那么多单态thunk。另一方面,类型类当然无法看到它可能生成的所有可能的常量,因此无法在类型类字典中分配单态thunk。
您必须明确提及您想要分配单态thunk的任何类型。
答案 1 :(得分:6)
可以向Typeable
添加n
约束,并为每个地面类型n
使用不同的记忆表。您可能需要大量利用Dynamic
和cast
,这是次优的。它也感觉有点hackish。
当然,在依赖类型的语言中,可以为地图(n : Num) -> [n]
建模,而不需要casts
中的Dynamic
。也许这样的东西可以模拟利用GADT和某种物化机制。
答案 2 :(得分:3)
如果您启用ConstraintKinds
和ExistentialQuantification
(或GADTs
),则可以隐藏类型类词典:
{-# LANGUAGE ConstraintKinds, ExistentialQuantification #-}
data Dict a = a => Dict
如果我们试试这个
fibs :: Num n => [n]
fibs = 1 : 1 : zipWith (+) fibs (drop 1 fibs)
fibs' :: [Integer]
fibs' = fibs
fibsWithDict :: Dict (Num n) -> [n]
fibsWithDict Dict = fs
where
fs = 1 : 1 : zipWith (+) fs (drop 1 fs)
fibs'' :: [Integer]
fibs'' = fibsWithDict Dict
在GHCi中我们看到了
λ> :set +s
λ>
λ> fibs !! 29
832040
(2.66 secs, 721235304 bytes)
λ>
λ> fibs !! 29
832040
(2.52 secs, 714054736 bytes)
λ>
λ>
λ> fibs' !! 29
832040
(2.67 secs, 713510568 bytes)
λ>
λ> fibs' !! 29
832040
(0.00 secs, 1034296 bytes)
λ>
λ>
λ> fibs'' !! 29
832040
(0.00 secs, 1032624 bytes)
所以fibs''
是三个立即记忆的唯一实现。
请注意,我们必须在Dict
构造函数上进行模式匹配。否则,我们会收到关于n
没有被限制为Num
实例的错误(就像我们的签名只是fibsWithDict :: a -> [n]
一样)。
这是一个完整的解决方案,因为您可以认为fibsWithDict Dict
是一个表达式,可以立即记住您抛出的任何类型(只要它是Num
的一个实例)。例如:
λ> (fibsWithDict Dict !! 29) :: Double
832040.0
(0.00 secs, 1028384 bytes)
编辑:看起来这里显式字典传递并不是必需的,可以通过ScopedTypeVariables
使用本地绑定隐式完成:
{-# LANGUAGE ScopedTypeVariables #-}
fibsImplicitDict :: forall a. Num a => [a]
fibsImplicitDict
= let fs :: [a]
fs = 1 : 1 : zipWith (+) fs (drop 1 fs)
in
fs
(感谢bennofs的见解!)