是否有自动方法在Haskell中记忆全局多态值?

时间:2014-07-31 11:30:13

标签: haskell memoization parametric-polymorphism

多态"常数",如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

......在优雅方面并不好。

有没有更好的方法来实现这样的备忘录?

3 个答案:

答案 0 :(得分:9)

出于相当技术原因,这是不可能的。类型类是开放的,因此多态常量在编译时不能“看到”有多少类型满足约束,因此它不能分配那么多单态thunk。另一方面,类型类当然无法看到它可能生成的所有可能的常量,因此无法在类型类字典中分配单态thunk。

您必须明确提及您想要分配单态thunk的任何类型。

答案 1 :(得分:6)

可以向Typeable添加n约束,并为每个地面类型n使用不同的记忆表。您可能需要大量利用Dynamiccast,这是次优的。它也感觉有点hackish。

当然,在依赖类型的语言中,可以为地图(n : Num) -> [n]建模,而不需要casts中的Dynamic。也许这样的东西可以模拟利用GADT和某种物化机制。

答案 2 :(得分:3)

如果您启用ConstraintKindsExistentialQuantification(或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的见解!)