我正在寻找一个功能
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ConstraintKinds #-}
memoC :: (c => a) -> (c => a)
这样生成的a
仅针对提供的约束进行一次计算。
如何制作某种类型a
的值,只有在出现某种约束证明c
时才能检查?
我长期以来一直在寻求记住表格价值的一般解决方案:
C a => a
其中C
是一些约束,a
范围超过所有类型。通过Typeable
上的a
约束以及一些智能构造函数,可以通过在Typeable a => b
上构建一个trie来安全地为TypeRep
的trie记忆。这个问题是关于更难的部分,在这样一个特里的叶子上放什么。
如果我们可以以某种方式将a
放入叶子中,则trie的叶子最初需要为某个具体类型C a => a
设置值a
,因为类的字典可以'从类型中查找。查找trie中的值将需要C a
的字典。这似乎相当于根据传入的字典修改了trie叶子上的值。
如果我们无法以某种方式将a
放入树叶中,那么对于单个C a => b
,树叶会有更加可怕的b
类型,并且在提供字典时我们会需要证明类型a
(以及字典)可以由b
中的内容确定,这不会比TypeRep
更强大。
很容易进入bag of evil构建一个构造函数来保持trie的叶子。如果每个约束只有一个字典可用,那么根据传入的字典修改trie叶子上保存的值并不是邪恶的。
对此的任何“解决方案”都可能是非常邪恶的。我假设任何约束都只有一个字典。可以为约束构造多个字典的反射give
s us another evil。
劝我脱离这种邪恶。
以下内容不应(并且不会)记住提供约束TracedC String
的结果。
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleInstances #-}
import Debug.Trace (trace)
class TracedC a where
tracedC :: () -> a -- The () argument keeps a from being memoized in the dictionary for `TracedC a`
instance TracedC [Char] where
tracedC _ = trace "tracedC :: String" "Yes"
newtype Memoized c a = Memoized { getMemoized :: c => a }
example :: Memoized (TracedC a) a
example = Memoized (tracedC ())
main = do
let memo = example :: Memoized (TracedC [Char]) String
putStrLn $ getMemoized memo
putStrLn $ getMemoized memo
输出
tracedC :: String
Yes
tracedC :: String
Yes
解决方案会接受类似的示例,但只会在仅输出
时评估tracedC () :: TracedC [Char] -> String
tracedC :: String
Yes
Yes
A map from types to values f a
可以在monadic memoization中使用,但有明显的副作用。
答案 0 :(得分:5)
我们围绕一个缺少约束和MVar
的值创建一个严格的构造函数。
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ConstraintKinds #-}
import System.IO.Unsafe (unsafePerformIO)
import Control.Concurrent.MVar
data UpToSingle c a = UpToSingle (c => a) !(MVar a)
它只会被智能构造函数和解构器使用。在模块中,我们不会导出UpToSingle
构造函数。
我们为它提供了一个聪明的构造函数;构造构造函数等同于分配MVar
。
upToSingle :: (c => a) -> UpToSingle c a
upToSingle a = UpToSingle a $ unsafePerformIO newEmptyMVar
我们还提供智能解构器。它使用那里的任何值或用提供的字典计算一个值。它依赖于c
有一个可能的字典。
fillMVar :: MVar a -> a -> IO a
fillMVar mvar a = do
tryPutMVar mvar a
readMVar mvar
withSingle :: c => UpToSingle c a -> a
withSingle (UpToSingle a mvar) = unsafePerformIO $ fillMVar mvar a
使用与问题中相同的示例跟踪代码。
{-# LANGUAGE FlexibleInstances #-}
import Debug.Trace (trace)
class TracedC a where
tracedC :: () -> a -- The () argument keeps a from being memoized in the dictionary for `TracedC a`
instance TracedC [Char] where
tracedC _ = trace "tracedC :: String" "Yes"
而UpToSingle
代替Memoized
,upToSingle
代替Memoized
构造函数,withSingle
代替getMemoized
example :: UpToSingle (TracedC a) a
example = upToSingle (tracedC ())
main = do
let memo = example :: UpToSingle (TracedC [Char]) String
putStrLn $ withSingle memo
putStrLn $ withSingle memo
我们得到了所需的输出
tracedC :: String
Yes
Yes
结合reflection显示UpToSingle
或Given
的邪恶。最后两行都应该打印相同的东西。通过替换它们都是give 9 (withSingle (upToSingle given))
。
main = do
let g1 = upToSingle given :: UpToSingle (Given Integer) Integer
let g2 = upToSingle given :: UpToSingle (Given Integer) Integer
print $ give 7 (withSingle g1)
print $ give 9 (withSingle g2)
print $ give 9 (withSingle g1)
他们实际打印以下内容:
7
9
7
give 7
在give 9
之前将Given Integer
字典传递给g1
而不是give 9
的{{1}}进行评估,并且会产生改变结果的副作用give 9 (withSingle (upToSingle given))
。 UpToSingle
假设词典是唯一的,或give
是构建新的非唯一词典的邪恶,这两者都是邪恶的。
当发现约束时,我们可以使用相同的延迟技巧来为Typeable a => f a
构建备忘录的叶子。从概念上讲,trie的叶子都是以下GDynamic
中的一个。
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Typeable
import Control.Monad (liftM)
data GDynamic f where
GDynamic :: Typeable a => f a -> GDynamic f
unGDynamic :: Typeable a => GDynamic f -> Maybe (f a)
unGDynamic (GDynamic f) = gcast f
在构建trie时,我们没有构建Typeable a
所需的GDynamic
个实例。我们只有一个TypeRep
。相反,我们将窃取访问该值时提供的Typeable a
实例。最高GDynamic
个实例的Typeable a
值为TypeRep
,值为forall a.
的定义以及用于保存实际MVar
的{{1}}
GDynamic
我们不会导出data UpToTypeable f = UpToTypeable TypeRep (forall a. Typeable a => f a) !(MVar (GDynamic f))
构造函数,而只导出智能构造函数和解构函数。构建UpToTypeable
后,我们会分配UpToTypeable
。
MVar
解构后,用户会提供upToTypeable :: TypeRep -> (forall a. Typeable a => f a) -> UpToTypeable f
upToTypeable r f = UpToTypeable r f $ unsafePerformIO newEmptyMVar
个实例。如果它与Typeable a
中存储的TypeRep
具有相同的UpToTypeable
,则我们接受该类型相同的证据,并使用提供的Typeable a
实例来填充{{1}的值}}
GDynamic
这应该是安全的,因为未来的GHC版本将禁止用户为withTypeable :: forall f a. Typeable a => UpToTypeable f -> Maybe (f a)
withTypeable (UpToTypeable r f mvar) = unsafePerformIO $ do
if typeRep (Proxy :: Proxy a) == r
then liftM unGDynamic $ fillMVar mvar (GDynamic (f :: f a))
else return Nothing
提供的实例。