Haskell中的多态性值图

发布于 2025-01-25 19:21:20 字数 1235 浏览 3 评论 0 原文

假设我有一个类,它为其成员类型的值声明了一些构造函数:

class C t where
  fromInt :: Int -> t
  fromString :: String -> t

进一步假设,我想使用这些多态性构造函数创建一堆值,然后将它们放入中>容器软件包。但重要的是,我希望这些值保持多态性并将其决定在其混凝土类型上延迟,直到从地图中提取它们为止。

newtype WrapC = WrapC (forall t . C t => t)

newtype MapC = MapC (Map String WrapC)

使用 rankntypes 扩展程序我可以定义这样的地图,以下定义见证了确实可以从中提取多态值的事实:

get :: C t => String -> MapC -> Maybe t
get key (MapC m) = fmap (\(WrapC t) -> t) $ Map.lookup key m

但是,当我想向地图添加一个值时,我正在面对类型错误,因为Haskell显然无法统一一些隐藏的类型变量。定义:

add :: C t => String -> t -> MapC -> MapC
add key val (MapC m) = MapC $ Map.insert key (WrapC val) m

无法与:

• Couldn't match expected type ‘t1’ with actual type ‘t’
  ‘t1’ is a rigid type variable bound by
    a type expected by the context:
      forall t1. C t1 => t1
    at src/PolyMap.hs:21:53-55
  ‘t’ is a rigid type variable bound by
    the type signature for:
      add :: forall t. C t => String -> t -> MapC -> MapC

直觉上我猜想这是隐藏在 wrapc 中的类型变量,无法统一。我不明白的是为什么需要成为。或我该如何使这个示例起作用…

Suppose I've got a class, which declares some constructors for values of its member types:

class C t where
  fromInt :: Int -> t
  fromString :: String -> t

Further suppose, I want to create a bunch of values using these polymorphic constructors and put them in a Map from containers package. But importantly, I want the values to remain polymorphic and deferr the decision on their concrete type until they are extracted from the map.

newtype WrapC = WrapC (forall t . C t => t)

newtype MapC = MapC (Map String WrapC)

Using RankNTypes extension I can define such a map and the following definition witnesses the fact that polymorphic values indeed can be extracted from it:

get :: C t => String -> MapC -> Maybe t
get key (MapC m) = fmap (\(WrapC t) -> t) $ Map.lookup key m

However, when I want to add a value to the map, I'm facing a type error, because Haskell apparently cannot unify some hidden type variables. The definition:

add :: C t => String -> t -> MapC -> MapC
add key val (MapC m) = MapC $ Map.insert key (WrapC val) m

fails to compile with:

• Couldn't match expected type ‘t1’ with actual type ‘t’
  ‘t1’ is a rigid type variable bound by
    a type expected by the context:
      forall t1. C t1 => t1
    at src/PolyMap.hs:21:53-55
  ‘t’ is a rigid type variable bound by
    the type signature for:
      add :: forall t. C t => String -> t -> MapC -> MapC

Intuitively I'm guessing that's the type variable hidden inside WrapC that cannot be unified. What I don't understand is why it needs to be. Or how can I make this example work…

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

掀纱窥君容 2025-02-01 19:21:20

您只需要将您的功能交付实际上是多态性的:

add :: String -> (forall t. C t => t) -> MapC -> MapC

但是我可能会提出一些笨拙的东西。你能摆脱这个吗?

type MapC = Map String (Either Int String)

get :: C t => String -> MapC -> Maybe t
get k m = either fromInt fromString <
gt; Map.lookup k m

无需类型的系统扩展。

You just need to hand your function something actually polymorphic:

add :: String -> (forall t. C t => t) -> MapC -> MapC

But I would propose something even dumber, maybe. Can you get away with this?

type MapC = Map String (Either Int String)

get :: C t => String -> MapC -> Maybe t
get k m = either fromInt fromString <
gt; Map.lookup k m

No type system extensions needed.

┼── 2025-02-01 19:21:20

让我通过解决最后一点来补充现有答案:

直觉上,我猜这是隐藏在 wrapc 内的类型变量,无法统一。我不明白的是为什么需要。

您可以通过查看添加 get 的类型签名来查看问题:

add :: C t => String -> t -> MapC -> MapC
get :: C t => String -> MapC -> Maybe t

您需要问的问题是“谁在选择 t的类型?”。

添加的类型签名说,谁调用 add 可以选择 t 是任何事物(只要 c c t 持有),该值将插入地图。

同样, get 的类型签名说,谁调用 get 可以选择 t 是任何事物(只要 C T 保留),该值将从地图中提取。

但是,这是无法使用的:如果我们称添加选择 t = a ,请在地图中插入值,然后调用 get 从地图中提取相同的值选择不同的 t = b ?这将等于更改值的类型,因此必须不允许。

任何解决方案都必须使两个呼叫者以某种方式就 t 达成一致。

一种解决方案是使的呼叫者添加存放一个多态值,而不仅仅是它们选择的某些特定 t 的值。 get 的呼叫者仍然可以免费选择任何 t

add :: String -> (forall t. C t => t) -> MapC -> MapC
get :: C t => String -> MapC -> Maybe t

另一个解决方案是保持添加的呼叫者免费选择他们喜欢的一个 t 。但是,在这种情况下, get 的呼叫者必须适应任何此类选择:结果类型仅是生存量化的,而不是普遍的

add :: C t => String -> t -> MapC -> MapC
-- pseudo-code:
--   get ::  String -> MapC -> (exists t. C t => Maybe t)
-- Since Haskell has no "exists", we need to encode it somehow, e.g.:
get :: String -> MapC -> SomeC

data SomeC = forall t . C t => SomeC (Maybe t)

Let me complement the existing answer by addressing the last point:

Intuitively I'm guessing that's the type variable hidden inside WrapC that cannot be unified. What I don't understand is why it needs to be.

You can see what is going wrong by looking at the type signatures for add and get:

add :: C t => String -> t -> MapC -> MapC
get :: C t => String -> MapC -> Maybe t

The question you need to ask is "who is choosing the type for t?".

The type signature for add says that whoever calls add can choose t to be anything (as long as C t holds), and that value will be inserted in the map.

Similarly, the type signature for get says that whoever calls get can choose t to be anything (as long as C t holds), and that value will be extracted from the map.

This, however, can not work: what if we call add choosing t=A, insert the value in a map, and then call get to extract the same value from the map choosing a different t=B? This would amount to changing the type of the value, hence must be disallowed.

Any solution must make the two callers agree on t in some way.

One solution is to make the caller of add deposit a polymorphic value, not just a value for some specific t they chose. The caller of get remains free to choose any t.

add :: String -> (forall t. C t => t) -> MapC -> MapC
get :: C t => String -> MapC -> Maybe t

Another solution is to keep the caller of add free to choose only one t of their liking. However, in such case, the caller of get must adapt to any such choice: the result type will only be existentially quantified instead of universally quantified.

add :: C t => String -> t -> MapC -> MapC
-- pseudo-code:
--   get ::  String -> MapC -> (exists t. C t => Maybe t)
-- Since Haskell has no "exists", we need to encode it somehow, e.g.:
get :: String -> MapC -> SomeC

data SomeC = forall t . C t => SomeC (Maybe t)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文