【问题标题】:How to use a proxy in Haskell? (probably using a higher-rank types extension)如何在 Haskell 中使用代理? (可能使用更高级别的类型扩展)
【发布时间】:2012-02-09 23:51:58
【问题描述】:

在过去的几个月里,我一直在努力学习 Haskell - 以前,我是一个看似永远的新手,对基础知识的了解非常有限。在尝试将我的新知识应用到从 hello-world 项目开始的几个步骤中时,我不断发现我想使用基于类型类的类似代理的模式。前几次,当我弄清楚它为什么不起作用时,我将其驳回为“好的——我可能找不到一个惯用的 Haskell 替代品,但这里的问题很可能是我使用了错误的方法语言”。但我发现我真的真的不喜欢不能做类似代理的事情。

试图更深入地了解为什么我不能使用代理,经过大量试验后,我终于想通 GHC 更高级别的类型扩展,也许我可以拥有代理。但我仍然不能让它工作,我不知道为什么。

这是我管理过的最好的代码...

{-# LANGUAGE RankNTypes #-}
module Test where

--  Simple type class based on parser combinators.
class Gen g where
  get :: g x -> [(x, g x)]

instance Gen [] where
  get [] = []
  get (x:xs) = [(x, xs)]

--  Proxy type - holds a pair containing...
--    - a value of some type that supports Gen
--    - a function to indicate when an item should be skipped
newtype PROXY nestedgen x = Proxy (nestedgen x, x -> Bool)

proxyskip :: Gen nestedgen => PROXY nestedgen r -> Bool
proxyskip (Proxy (g, predf)) = case get g of
                                 []        -> False
                                 ((r,_):_) -> predf r

proxyget :: Gen nestedgen => PROXY nestedgen r -> [(r, PROXY nestedgen r)]
proxyget pr@(Proxy (sg, predf)) = if proxyskip pr
                                    then [(r2, g2) | (_, g1) <- get sg, (r2,g2) <- proxyget (Proxy (g1, predf))]
                                    else [(r3, Proxy (g3, predf)) | (r3,g3) <- get sg]

--  Instance of Gen for PROXY - get skips items where appropriate
instance Gen nestedgen => Gen (PROXY nestedgen) where
  get = proxyget

--  Test "parser"
--  Get the specified number of items, providing them as a list (within
--  the list of nondeterministic (result, state) pairs).
getN :: Gen g => Int -> g x -> [([x], g x)]
getN n g | (n < 0)  = error "negative n"
         | (n == 0) = [([], g)]
         | True     = [(r1:r2, g2) | (r1, g1) <- get g, (r2, g2) <- getN (n-1) g1]

--  Wrap some arbitrary "parser" in a PROXY that skips over the letter 'l'
proxyNotL :: Gen gb => gb Char -> PROXY gb Char
proxyNotL gg = (Proxy (gg, \ch -> (ch /= 'l')))

call_f0 :: Gen gb => (Gen ga => ga Char -> [(r, ga Char)]) -> gb Char -> [(r, PROXY gb Char)]
call_f0 f0 g0 = f0 (proxyNotL g0)

test :: Gen gb => (Gen ga => ga Char -> [(r, ga Char)]) -> gb Char -> [(r, gb Char)]
test f0 g0 = [(r, g2) | (r, Proxy (g2, _)) <- call_f0 f0 g0]

最后剩下的错误发生在call_f0 f0 g0 = f0 (proxyNotL g0)...行上。

[1 of 1] Compiling Test             ( Test.hs, Test.o )

Test.hs:44:21:
    Could not deduce (ga ~ PROXY gb)
    from the context (Gen gb)
      bound by the type signature for
                 call_f0 :: Gen gb =>
                            (Gen ga => ga Char -> [(r, ga Char)])
                            -> gb Char
                            -> [(r, PROXY gb Char)]
      at Test.hs:44:1-33
      `ga' is a rigid type variable bound by
           the type signature for
             call_f0 :: Gen gb =>
                        (Gen ga => ga Char -> [(r, ga Char)])
                        -> gb Char
                        -> [(r, PROXY gb Char)]
           at Test.hs:44:1
    Expected type: ga Char
      Actual type: PROXY gb Char
    In the return type of a call of `proxyNotL'
    In the first argument of `f0', namely `(proxyNotL g0)'
    In the expression: f0 (proxyNotL g0)

查看有问题的函数...

call_f0 :: Gen gb => (Gen ga => ga Char -> [(r, ga Char)]) -> gb Char -> [(r, PROXY gb Char)]
call_f0 f0 g0 = f0 (proxyNotL g0)

f0 函数是(如果我正确理解更高级别的类型)作为参数传递的多态函数,类型为Gen ga =&gt; ga Char -&gt; [(r, ga Char)]。在翻译成 C 的术语中,调用者传入了一个函数指针,但没有提供 vtable 指针。

proxyNotL 函数返回PROXY gb Char 类型的东西,并且有一个实例声明instance Gen nestedgen =&gt; Gen (PROXY nestedgen) where ...,因此PROXY gb Char 实例Gen 提供gb 实例Gen,它根据类型执行call_f0的签名。

基本上,据我所知,GHC 应该说“我可以提供f0 需要的vtable...嗯...是的,因为PROXY gb 实例Gen,我知道@987654338 @ 和 gb,是的,我可以”。

那么...为什么 GHC 拒绝统一 gaProxy gb?为什么 GHC 拒绝调用多态函数,其参数值应该由该函数的多态类型支持?

或者,我在这里完全误解了什么?

【问题讨论】:

    标签: haskell proxy ghc


    【解决方案1】:

    您只需要通过将通用量词forall ga. 添加到类型签名来明确指定函数f0 必须是多态的:

    call_f0 :: Gen gb => (forall ga. Gen ga => ga Char -> [(r, ga Char)]) -> gb Char -> [(r, PROXY gb Char)]
    test :: Gen gb => (forall ga. Gen ga => ga Char -> [(r, ga Char)]) -> gb Char -> [(r, gb Char)]
    

    否则,GHC 将在最外层放置一个隐含的forall ga.,这意味着调用者将决定应该使用哪个ga,而GHC 已经正确推断出这些函数必须能够选择自己@ 987654326@ 应该是PROXY gb

    换句话说,当使用RankNTypes 要求多态参数时,您必须始终使用显式量词来指定它。

    【讨论】:

    • 经过测试并且可以编译,但是:将其加载到 gchi 中,我没有得到我希望的结果。不过,可能只是某个地方的一些愚蠢的错误-我相信我能弄明白。谢谢。
    • Bug 已解决 - 正如预期的那样愚蠢的错误 - 在 proxyget 中,我有 thenelse 错误的方式。现在,我得到了我想要的结果。再次感谢您的帮助 - 尤其是对于将 forall 放在不同位置赋予不同含义的见解。
    【解决方案2】:

    我不太清楚您在这种情况下所说的“代理”是什么意思。但我想您可能指的是表示Gen 实例的数据类型。

    采用Gen 类型类

    class Gen g where
        get :: g x -> [(x, g x)]
    

    forall g. Gen g =&gt; g x 类型应该可以用具体的数据类型(构造函数)来表示。如果你有这样的值,比如a唯一有效的操作是应用get,给我们:

    a :: forall g. Gen g => g x
    get a :: forall g. [(x, g x)]
    

    所以我们看到g x中包含的信息与[(x, g x)]中包含的信息是一样的;如果我们想要一个可以表示任何这样的g 的类型,我们就取最大不动点:

    newtype G x = G { unG :: [(x, G x)] }
    

    G 现在应该能够代表任何Gen

    toG :: Gen g => g x -> G x
    toG = G . map (second toG) . get
    

    它本身就是Gen的一个实例

    instance Gen G where
        get (G as) = as
    

    这和你想要的一样吗?

    【讨论】:

    • 这是我在清醒时需要凝视和思考的事情之一。我喜欢它似乎不需要扩展,但我不确定它是否能满足我的需要。定义实例 Gen 不是重点的任意类型 - 我已经为 [] 定义了一个实例。关键是一种类型必须充当另一种类型的代理 - 包装另一种类型的值,并将包装调用转换为包装器 get 内的包装值 get
    • 在一个实际示例中,代理可能会记录为支持最长匹配选择而进行了多少次包装值get 调用。每种选择使用什么组合解析器并不重要,只要所有这些都基于单个原语get 解析器。这基本上是面向对象的代理设计模式——尽管代理模式实际上比 OOP 早得多,例如在 C++ 或 OOP 广为人知之前,通常在 C 中使用函数指针和类型双关来实现“多态性”。
    • 您的 toG 似乎做了一些包装,但我没有看到添加额外功能层的明显方法(例如,计数获取 - 或者,在问题的测试中,跳过在某些项目上)这就是使用代理的全部意义所在。但也许我只是不考虑直接 ATM。
    猜你喜欢
    • 1970-01-01
    • 2020-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-24
    • 2016-10-09
    • 1970-01-01
    相关资源
    最近更新 更多