【问题标题】:`coerce` and instantiation of type variables`coerce` 和类型变量的实例化
【发布时间】:2017-09-04 07:36:33
【问题描述】:

考虑以下 GHCi 会话:

>:set -XTypeApplications
>import Data.Map.Strict
>import GHC.Exts
>newtype MySet a = MySet (Map a ())
>let member' :: Ord a => a -> MySet a -> Bool; member' = coerce member

<interactive>:21:57: error:
    * Couldn't match representation of type `a0' with that of `()'
        arising from a use of `coerce'
    * In the expression: coerce member
      In an equation for member': member' = coerce member
>let member' :: Ord a => a -> MySet a -> Bool; member' = coerce (member @_ @())

我有一种预感:类型检查器需要满足 Coercible (Ord a =&gt; a -&gt; Map a b -&gt; Bool) (Ord a =&gt; a -&gt; MySet a -&gt; Bool) 并且无法在此约束中将 b 实例化为 ()

还有比-XTypeApplications 更优雅的方法吗?

编辑:我特别在寻找能够处理类型中出现许多MySet a 的解决方案,例如union :: Ord a =&gt; MySet a -&gt; MySet a -&gt; MySet a

【问题讨论】:

  • member' k s = member k (coerce s) 怎么样?
  • 好主意,但是这对于union 来说变得非常冗长(其中MySet 在类型中出现多次)并且对于fromList 根本不可能。
  • 我认为这可能是 GHC 中的一个相当大的错误,当然值得一张票。或者在牛津和理查德谈谈 :-)
  • @JoachimBreitner,我怀疑这很难解决。看我的回答。当然,你和理查德都比我知道的多。

标签: haskell unification coerce


【解决方案1】:
member :: Ord a => a -> Map a b -> Bool
member' :: Ord a => a -> MySet a -> Bool

GHC 需要接受

Coercible (Map a b) (MySet a)

它看到了

Coercible (MySet a) (Map a ())

这让它需要

Coercible (Map a ()) (Map a b)

导致

Coercible () b

但是b 是什么?这是模棱两可的。在这种情况下,b 是什么并不重要,因为根据参数,member 不可能关心。因此,选择b ~ () 并轻松解决强制转换是完全合理的。但是 GHC 在类型推断中一般不会进行这样的参数化分析。我怀疑改变它可能很棘手。尤其是,任何时候类型推断“猜测”,都有可能猜错并在其他地方阻止推断。这是一大罐虫子。

至于你的问题,我没有好的解决办法。当您有多个具有相似模式的函数时,您可以将它们抽象出来,但您仍然会面临很大的烦恼。

【讨论】:

  • 这听起来有点像我的直觉告诉我的,但我不确定。谢谢!使用-XTypeApplications 相当乏味,但它确实有效:github.com/sgraf812/pomaps/blob/…
  • 顺便说一句,我认为第三个和第四个Coercibles 已切换。并不是说它改变了这个想法。
  • @SebastianGraf,如果您要包装整个模块,您可能会发现使用完整类型签名比使用类型应用程序更容易。 foo = coerce (M.foo :: theoldthing this that) :: forall this that. thenewthing this that)。以这种方式进行更多的复制/粘贴,而无需考虑将哪种类型的参数放在哪里。
【解决方案2】:

TypeApplications 的解决方案非常简单:

{-# LANGUAGE TypeApplications #-}

import Data.Coerce
import qualified Data.Map as M

newtype Set a = Set (M.Map a ())

member :: Ord a => a -> Set a -> Bool
member = coerce (M.member @_ @())

union :: Ord a => Set a -> Set a -> Set a
union = coerce (M.union @_ @())

请注意,某些功能将需要或多或少的通配符,例如

smap :: (Ord b) => (a -> b) -> Set a -> Set b
smap = coerce (M.mapKeys @_ @_ @())

要确定您必须如何准确地指定类型应用程序(除了反复试验),请使用

>:set -fprint-explicit-foralls
>:i M.mapKeys
M.mapKeys ::
  forall k2 k1 a. Ord k2 => (k1 -> k2) -> M.Map k1 a -> M.Map k2 a

您从:i 获得的可变顺序与TypeApplications 使用的顺序相同。

请注意,您不能将 coerce 用于 fromList - 这不是限制,只是没有意义:

fromList :: Ord a => [a] -> Set a
fromList = coerce (M.fromList @_ @())

这给出了错误

* Couldn't match representation of type `a' with that of `(a, ())'

你可以在这里做的最好的可能是

fromList :: Ord a => [a] -> Set a
fromList = coerce (M.fromList @_ @()) . map (flip (,) ())

【讨论】:

  • 注意:OP 明确表示他们知道 TypeApplications 解决方案,但我认为它“足够简单” - 所以这个答案本质上是“不,没有更优雅的方式”。但这似乎很大程度上是基于意见的,所以我将答案本身表述为一种(简单!)方法,可以用来机械地编写这样的代码,而无需费力思考如何去做。
  • :set -fprint-explicit-foralls 听起来就像我在实现github.com/sgraf812/pomaps/blob/… 时知道的那样。感谢您的提示,但我认为另一篇文章更直接地回答了这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-31
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
  • 1970-01-01
相关资源
最近更新 更多