Cir*_*dec 10 haskell constraints memoization
我正在寻找一个功能
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ConstraintKinds #-}
memoC :: (c => a) -> (c => a)
Run Code Online (Sandbox Code Playgroud)
这样得到的a
s仅针对所提供的约束进行一次评估.
如何创建某种类型的值,a
只有在存在某些约束的证明时才能检查c
?
我一直在追求一种通用的解决方案来记住表格的价值:
C a => a
Run Code Online (Sandbox Code Playgroud)
所有类型的C
约束和a
范围都在哪里.通过Typeable
约束a
和一些智能构造器,可以Typeable a => b
通过在TypeRep
s上构建trie 来安全地记住trie的脊柱.这个问题是关于更难的部分,在这样一个特里的叶子上放什么.
如果我们可以以某种方式a
进入叶子,则trie的叶子最初需要具有C a => a
某种具体类型的值a
,因为类的字典无法从类型中查找.查找trie中的值将需要字典C a
.这似乎等于根据传入的字典修改trie叶子上保存的值.
如果我们不能以某种方式a
进入叶子,叶子将会有一个更加可怕C a => b
的单一类型b
,并且,在提供字典时,我们需要证明类型a
(因此字典)可以由什么是举行b
,这将不会比一个更强大TypeRep
.
它很容易进入邪恶的袋子,建立一个构造者来抓住特里的叶子.如果每个约束只有一个字典可用,那么根据传入的字典修改trie叶子上保存的值并不是邪恶的.
任何"解决方案"都可能是非常邪恶的.我假设任何约束都只有一个字典.反思give
是另一个可以为约束构造多个字典的邪恶.
劝我脱离这种邪恶.
以下内容不应该(也不会)记住提供约束的结果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
Run Code Online (Sandbox Code Playgroud)
输出是
tracedC :: String
Yes
tracedC :: String
Yes
Run Code Online (Sandbox Code Playgroud)
解决方案将允许类似的示例,但仅评估tracedC () :: TracedC [Char] -> String
一次输出
tracedC :: String
Yes
Yes
Run Code Online (Sandbox Code Playgroud)
从类型到值的映射,f a
可用于具有明显副作用的monadic memoization.
我们围绕一个缺少约束和一个值的值创建一个严格的构造函数MVar
.
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ConstraintKinds #-}
import System.IO.Unsafe (unsafePerformIO)
import Control.Concurrent.MVar
data UpToSingle c a = UpToSingle (c => a) !(MVar a)
Run Code Online (Sandbox Code Playgroud)
它只会被智能构造函数和解构器使用.在模块中,我们不会导出UpToSingle
构造函数.
我们为它提供了一个聪明的构造函数; 构造构造函数等同于分配构造函数MVar
.
upToSingle :: (c => a) -> UpToSingle c a
upToSingle a = UpToSingle a $ unsafePerformIO newEmptyMVar
Run Code Online (Sandbox Code Playgroud)
我们还提供智能解构器.它使用那里的任何值或用提供的字典计算一个值.它依赖于有一个可能的字典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
Run Code Online (Sandbox Code Playgroud)
使用与问题中相同的示例跟踪代码.
{-# 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"
Run Code Online (Sandbox Code Playgroud)
而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
Run Code Online (Sandbox Code Playgroud)
我们得到了理想的输出
tracedC :: String
Yes
Yes
Run Code Online (Sandbox Code Playgroud)
结合反思任何一个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)
Run Code Online (Sandbox Code Playgroud)
他们实际打印以下内容:
7
9
7
Run Code Online (Sandbox Code Playgroud)
在give 7
该评估之前give 9
通过不同的Given Integer
字典g1
比give 9
会,不得不改变的结果的副作用give 9 (withSingle (upToSingle given))
.要么UpToSingle
假设词典是独特的,要么give
是构建新的非独特词典是邪恶的,这两者都是邪恶的.
当发现约束来构建备忘录的叶子时,我们可以使用相同的延迟技巧Typeable a => f a
.从概念上讲,trie的叶子都是以下GDynamic
s之一.
{-# 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
Run Code Online (Sandbox Code Playgroud)
在构造trie时,我们没有Typeable a
构造GDynamic
s 所需的实例.我们只有一个TypeRep
.相反,我们将窃取Typeable a
访问值时提供的实例.一个GDynamic
价值高达一Typeable a
实例是TypeRep
,价值的定义forall a.
和MVar
保持实际GDynamic
.
data UpToTypeable f = UpToTypeable TypeRep (forall a. Typeable a => f a) !(MVar (GDynamic f))
Run Code Online (Sandbox Code Playgroud)
我们不导出UpToTypeable
构造函数,而只导出智能构造函数和解构函数.当UpToTypeable
构建我们分配MVar
.
upToTypeable :: TypeRep -> (forall a. Typeable a => f a) -> UpToTypeable f
upToTypeable r f = UpToTypeable r f $ unsafePerformIO newEmptyMVar
Run Code Online (Sandbox Code Playgroud)
当它被解构时,用户提供一个Typeable a
实例.如果它TypeRep
与UpToTypeable
我们接受的那个相同,那就是证明类型相等并使用提供的Typeable a
实例来填充的值GDynamic
.
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
Run Code Online (Sandbox Code Playgroud)
这应该是安全的,因为未来的GHC版本将禁止用户提供的实例Typeable
.