【问题标题】:Laziness and polymorphic values惰性和多态值
【发布时间】:2012-04-27 11:26:37
【问题描述】:

(以下,将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]

这完全是同构的(毕竟,唯一可以用 ShowValshow 它)。

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

ShowVal 等价于存在元组exists a. (a -> String, a), 其中函数是Show 字典。

可以为Read 进行类似的构造:

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

注意,因为read 的返回值是多态的,所以ReadVal 是 普遍的而不是存在的(这意味着我们在 全部,因为 Haskell 具有一流的通用性;但我们将在这里使用它 突出显示与Show 的相似之处。

我们也可以列个名单:: [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 (CPS 风格的表示)。这里Read 字典由用户提供 ReadVal 的而不是生产者的,因为返回值是 多态而不是参数。

但是,在这两种表示中,我们都没有得到自动 我们用ShowString 表示中得到的记忆。让我们这么说 read 因为我们的类型是一个昂贵的操作,所以我们不想计算它 同一类型的同一String 上不止一次。

如果我们有一个封闭的类型,我们可以这样做:

data ReadVal = ReadVal { asInt :: Int, asChar :: Char, asBool :: Bool }

然后使用一个值

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

或者类似的东西。

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

(让 GHC 自动执行此操作,类似于 Show 的情况,将是 理想,如果有可能的话。更明确的记忆方法—— 也许通过添加Typeable 约束? -- 也可以。)

【问题讨论】:

  • 您的ReadVal 代码毫无意义。你写ReadVal "4" 好像它与整数4 有关。为什么不讨论ReadVal 4呢?一旦你意识到ReadVal 4 是可能的,很明显[ReadVal] 根本不与[String] 同构。
  • 你说得对——我的意思是ReadVal (read "4")。我应该在发布之前进行类型检查...我会更新问题以反映这一点,谢谢。 (不过,ReadVal 4 也是无效的,因为ReadVal 的参数需要比Num 更具多态性。)
  • 我想到了一个像 data ReadVal = forall a. Read a => ReadVal a 这样的定义,没有 GADT。有了这个定义ReadVal 4 是可能的。我更仔细地查看了您的定义,我承认我根本不理解它。
  • 我的定义等同于data ReadVal = ReadVal (forall a. Read a => a)——即,普遍的,而不是存在的。它根本不需要包装器;我只是把它放在那里以显示与ShowVal 的相似性。
  • 像您描述的那样存在主义的ReadVal 不会很有用——您将无能为力。它就像一个元组exists a. (String -> a, a)

标签: haskell polymorphism lazy-evaluation memoization


【解决方案1】:

懒惰使这特别好,因为对于列表中的每个值,显示的结果都会自动记忆,因此不会多次计算字符串(并且根本不会计算未使用的字符串)。

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

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

【讨论】:

  • 对——我的意思只是分享。如果您有s :: String; s = show x,如果 x 被多次使用,您将自动共享show 计算。 s :: ShowVal; s = ShowVal x 无法做到这一点,就像exists a. (a -> String, a)。然而,因为Read 我们有一个伪函数而不是一个值,所以我们需要实际的记忆——没有合理的共享方式,即使该值只用作一种类型。
【解决方案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")

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-08
    • 2021-12-23
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多