如果我尝试使用 generic-lens 包中的 @ 符号访问某个类型的字段,GHC 会提示启用 DataKinds 扩展。你能用简单的英语给我解释一下 generic-lens 和 DataKinds 是什么关系吗?

谢谢

最佳答案

DataKinds 是一个扩展,允许将数据类型提升到类型级别。这恰恰意味着是一种蠕虫(这是通往依赖类型的一个步骤),所以我将尝试将这个解释集中在为什么 generic-lens 使用它上(但请记住,我正在简化这里)。

首先,快速绕道。以下表达式的类型是什么?

mempty
mempty 来自 Monoid 类。然而,与大多数方法相比,它不接受任何参数。那么 Haskell 如何知道将其实例化为哪种类型呢?以下所有情况均属实:

mempty == []
mempty == Sum 0
mempty == Any False

简而言之,Haskell(或 GHC)会推断表达式的类型,并使用该推断来选择正确的实现。有时,推理不起作用。例如,在下面的表达式中:

print mempty

我们必须明确给出类型,如下所示:

print (mempty :: [Int])
@ 符号是一种语法,用于应用通常会推断出的任何类型。所以,在这种情况下,我们可以这样写:

print (mempty @[Int])

不过,它与 :: 不同: @ 符号专门填充了 ghc 试图猜测的第一个类型漏洞。所以我们可以在打印品前面同样应用它:

print @[Int] mempty

因此,您可以看到 @ 是我们将类型应用于表达式的一种方式。然而,它真正让我们做的是从一个类型中获取一个值(很容易)。例如:

{-# LANGUAGE TypeApplications, AllowAmbiguousTypes #-}

class TypeName a where
    name :: String

instance TypeName Int where
    name = "Hello, I'm an Int!"

instance TypeName Bool where
    name = "Bool!"

name @Int

换句话说,我们可以让类型级别的程序产生值级别的结果。这就是 generic-lens 的用武之地。这个包将它用于字段。当您有如下类型时:

data Person
  = Person
  { name :: String
  , age  :: Int
  }

您可以生产两个镜头,一个称为 _name ,另一个称为 _age (或其他)。 generic-lens 做的稍微聪明一点:它有一个函数( fieldLens ),你可以像这样使用:

-- age lens
fieldLens @"age"

最后,这里是我们需要 DataKinds 的地方。传统上来说,在类型级别只允许使用类型,这就是 @ 符号所处理的。不过,上面是一个字符串:解除这个限制正是 DataKinds 所做的。

最后,从技术上讲,您不需要 DataKinds 来伪造上述行为。您可以再次使用类型。事实上,在 DataKinds 之前,人们曾经做过如下事情:

data AgeField = DontConstructMe

fieldLens @AgeField

关于haskell - generic-lens 和 DataKinds GHC 扩展的关系,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56088579/

10-15 19:18