懒惰和多态的价值观

时间:2012-04-16 05:05:52

标签: haskell polymorphism lazy-evaluation memoization

(对于以下内容,请将ShowRead简化为

class Show a where show :: a -> String
class Read a where read :: String -> a

并假设read永远不会失败。)

众所周知,人们可以制作一种存在主义的形式

data ShowVal where
    ShowVal :: forall a. Show a => a -> ShowVal

然后构建一个“异构列表”:: [ShowVal],例如

l = [ShowVal 4, ShowVal 'Q', ShowVal True]

众所周知,这是相对无用的,因为相反,人们可以 只需构建一个列表:: [String],例如

l = [show 4, show 'Q', show True]

这是完全同构的(毕竟,唯一一个可以用a做的事情 ShowValshow。)

懒惰使这特别好,因为对于列表中的每个值, show的结果会自动记忆,因此计算的String不会超过String 一次(并且未使用的ShowVal根本不计算。)

exists a. (a -> String, a)相当于存在元组Show, 函数是Read字典。

可以为data ReadVal where ReadVal :: (forall a. Read a => a) -> ReadVal

制作类似的构造
read

请注意,因为ReadVal的返回值是多态的,Show是。{ 普遍而不是存在(这意味着我们并不真的需要它 所有,因为Haskell拥有一流的普遍性;但我们会在这里使用它 突出显示与:: [ReadVal])的相似之处。

我们还可以列出l = [ReadVal (read "4"), ReadVal (read "'Q'"), ReadVal (read "True")]

Show

:: [ReadVal]一样,列表:: [String]与列表l = ["4", "'Q'", "True"] 同构, 比如

String

(我们总是可以使用

返回原来的newtype Foo = Foo String instance Read Foo where read = Foo
Read

因为ReadVal类型类已打开。)

forall a. (String -> a) -> a相当于通用函数Read (CPS式表示)。这里ReadVal字典由用户提供 String而不是生产者,因为返回值是 多态而不是参数。

但是,在这些表示中,我们都没有自动获取 我们使用Showread表示中获得的记忆。我们这样说吧 对于我们的类型String是一项昂贵的操作,所以我们不想计算它 在同一类型data ReadVal = ReadVal { asInt :: Int, asChar :: Char, asBool :: Bool } 上不止一次。

如果我们有封闭类型,我们可以做类似的事情:

ReadVal { asInt = read s, asChar = read s, asBool = read s }

然后使用值

ReadVal

或类似的东西。

但在这种情况下 - 即使我们只使用String作为一种类型 - 每次使用该值时都将解析ReadVal。有一个简单的方法 在保持Show多态的同时得到记忆?

(让GHC自动执行此操作,与Typeable情况类似,即可 理想的,如果它在某种程度上是可能的。更明确的记忆方法 - 也许是通过添加{{1}}约束? - 也没关系。)

2 个答案:

答案 0 :(得分:5)

  

Laziness使这一点特别好,因为对于列表中的每个值,show的结果会自动记忆,因此不会多次计算任何String(并且根本不会计算未使用的字符串)。 / p>

这个前提是不正确的。引擎盖下没有神奇的备忘录表。

懒惰意味着不需要的东西,不计算。这并不意味着所有计算值都是共享的。您仍然需要引入显式共享(通过您自己的表)。

答案 1 :(得分:2)

这是更明确的方法的实现;它需要Typeable,因为否则没有任何东西可以锁定备忘录表。我基于uglymemo的备忘录代码;可能有一种方法可以使用纯记忆,但我不确定。这很棘手,因为你必须构造任何forall a. (Read a, Typeable a) => ...创建的隐式函数的 表,否则你最终会在每次调用时构造一个表,这是无用的。

{-# LANGUAGE GADTs, RankNTypes #-}

import Data.Dynamic
import Control.Concurrent.MVar
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import System.IO.Unsafe

data ReadVal where
    ReadVal :: { useReadVal :: forall a. (Read a, Typeable a) => a } -> ReadVal

mkReadVal :: String -> ReadVal
mkReadVal s = unsafePerformIO $ do
    v <- newMVar HM.empty
    return $ ReadVal (readVal v)
  where
    readVal :: (Read a, Typeable a) => MVar (HashMap TypeRep Dynamic) -> a
    readVal v = unsafePerformIO $ do
        m <- readMVar v
        let r = read s  -- not evaluated
        let typeRep = typeOf r
        case HM.lookup typeRep m of
            Nothing -> do
                modifyMVar_ v (return . HM.insert typeRep (toDyn r))
                return r
            Just r' -> return $ fromDyn r' (error "impossible")