类型类函数的显式 forall

Posted

技术标签:

【中文标题】类型类函数的显式 forall【英文标题】:Explicit forall on a type class function 【发布时间】:2019-02-14 22:34:08 【问题描述】:

从 ghc-8.0 开始,我们有了一个非常好的扩展,称为 TypeApplications。这允许我们而不是:

λ> show (5 :: Int)
"5"

这样做:

λ> :set -XTypeApplications
λ> show @Int 5
"5"

这真的很酷。当我们添加更多类型变量时,它会变得更加复杂,但是有一些规则可以用来确定确切的顺序,并且它们有很好的记录:

showFooBar :: (Show a, Show b) => a -> b -> String
showFooBar a b = show a ++ " and " ++ show b

所以在上面的函数中,我们将首先提供a,然后提供b

λ> showFooBar @Int @Double 3 4
"3 and 4.0"

很好,但是如果我想更改顺序怎么办?没问题,我们可以使用ExplicitForAll 扩展名(或其他暗示)来指定它:

-# LANGUAGE ExplicitForAll #-

showFooBar :: forall b a . (Show a, Show b) => a -> b -> String
showFooBar a b = show a ++ " and " ++ show b

现在我们颠倒了我们要应用的类型的顺序:

λ> showFooBar @Int @Double 3 4
"3.0 and 4"

问题是我似乎无法弄清楚如何对属于类型类的函数实现相同的效果。考虑这个例子:

-# LANGUAGE MultiParamTypeClasses #-

class (Show a, Show b) => FooBar a b where
  fooBarClassFunc :: a -> b -> String

我现在不能将forall 放在函数上(例如fooBarClassFunc :: forall a b . a -> b -> ..,因为这会改变函数的含义并且显然无法编译。

那么,问题来了,如何在类型类方法中为TypeApplication的目的改变类型变量的顺序?

编辑

以防万一,我尝试了InstanceSigs 扩展,它完全忽略了forall 类型变量的顺序,就TypeApplications 而言,这是一件好事,否则我们最终会出现以下行为由实例决定,而不是类。

【问题讨论】:

我不认为你可以。但是您始终可以将fooBarClassFunc' 设为该方法,然后将fooBarClassFunc :: forall b a. ... 定义为常规函数。对用户来说效果一样,对实例化器来说有点奇怪…… @liqui 是的,当然。但是我更好奇它是否可能在类型类本身上,也许是一些特殊的语法或我不知道的东西。我也不需要特别解决这个问题,这只是纯粹的好奇。 我天真地认为QuantifiedConstraints 也允许指定这个,但它似乎不允许像class forall b a . (C a, C b) => K a b where 这样的东西。此外,为了最大程度的通用性,人们可能希望顺序因方法而异。 请记住,您仍然可以在某些情况下更改参数顺序,例如你可以做class X t where func :: forall a b. t a -> t b -> t (a, b) 。你不能用t 来做到这一点,这是定义类型类的类型。并不是说这很有帮助... 【参考方案1】:

为了TypeApplication在类型类方法中的目的,你如何改变类型变量的顺序?

我认为@luqui 的回答已经足够好了。但为什么不这样:

class (Show b, Show a) => FooBar b a where
  fooBarClassFunc :: a -> b -> String

您只有一个方法,因此将参数顺序驱动到类的唯一考虑因素是方法内部的TypeApplication

如果您有两个或多个方法希望TypeApplication 的顺序不同(@chi 的观点,但为什么?),那么对于其他方法,要么是 luqui 的建议,要么(等效地)另一个类超类约束和默认实现。

class (Show a, Show b, FooBar b a) => OtherFooBar a b where
  otherFooBarClassFunc :: a -> b -> String
  otherFooBarClassFunc = otherFooBarClassFunc'  -- default
instance -# NOOVERLAPPABLE #- OtherFooBar a b  where   -- take default

(假设otherFooBarClassFunc' 是在主类中定义的;这就是真正的实例定义所在。)

当然,one method per class 有很多话要说。

-# NOOVERLAPPABLE #- wot we don't'ave is my little in-joke.

【讨论】:

没有必要添加另一个类来解决问题,添加一个函数otherFooBarClassFunc :: forall b a . FooBar a b => a -> b -> String,其中otherFooBarClassFunc = fooBarClassFunc 就足够了。我的问题涉及附加到类的方法,而不是从它派生的东西。

以上是关于类型类函数的显式 forall的主要内容,如果未能解决你的问题,请参考以下文章

发出属性的显式接口实现

C++17 中的显式默认构造函数

我应该如何确定查询返回到 GraphQL 接口类型的显式类型?

正确使用函数的显式模板实例化?

Scala 中的显式类型转换

在 C# 中引用类型的内存方面的显式转换解释