如何识别 Servant 所需的扩展

How to identify required extensions for Servant

我正在阅读 servant tutorial 但不明白代码的哪些部分使用了哪个扩展。本教程首先在文件开头添加 ~10 个扩展名,但第一个示例只需要 3 个。当我在 Servant 中实现自己的服务器时,我想使用所需的最少扩展名。有没有一种方法可以确定所需的最少扩展?

我不知道是否有比“禁用所有内容并根据编译器抱怨的内容一一启用它们”更好的方法

根据@GeorgeLyubenov 的回答,让编译器告诉您需要什么是完全允许的。 80-90% 的情况下,编译器错误消息会建议扩展,如果这能解决您的问题。在某些情况下它无法弄清楚,您只需要学习如何识别这些情况。当我在编写代码时,我经常会添加比我需要的更多的扩展,因为我会尝试然后放弃它们。最后,如果我想 trim 下扩展集,我只是尝试一个一个地删除它们,看看编译器是否抱怨(与 George 的方法相反)。这对于包含大量推荐扩展的教程或包非常有用,因为您可以将它们全部包括在内,然后尝试一个一个地删除它们。它有助于使用 IDE 或编辑器模式(我使用 Emacs dante)可以快速键入检查文件(例如,每次保存时),因此您不必手动 运行 GHC 20次。

据我所知,没有任何编译器标志可以转储使用的扩展列表,因此试错法是最好的方法...

...除非您真的想尝试理解扩展名的含义。这并不是说他们无缘无故地启用了编译器代码的随机位。它们启用了有据可查、易于理解的功能,虽然您可能无法理解和记住所有这些功能,但不难理解其中的大部分功能。

从者教程中给出的列表中:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}

两个对于编写 Servant API 至关重要,例如:

type UserAPI1 = "users" :> Get '[JSON] [User]

最容易理解的是TypeOperators,就是让你在API类型中使用像:>这样的中缀运算符。没有它,您需要将 API 写为:

type UserAPI1 = (:>) "users" (Get '[JSON] [User])

这几乎违背了拥有良好的基于​​运算符的语法的初衷。第二个关键扩展 DataKinds 有点难以理解,但它允许您使用值,例如字符串 "users"(和“已勾选的列表”'[...],尽管不是未选中的列表 [User]JSON 类型本身),作为类型。

因此,任何指定 API 的 Servant 程序几乎肯定需要:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}

如果你曾经写过 ... deriving (Generic),你需要 DeriveGeneric。在 Servant 程序中,如果您想使用从您的数据类型自动派生的 ToJSON 实例来服务 JSON,则最有可能出现这种情况。对于教程中的User数据类型,实例:

instance ToJson User

要求 User 有一个 Generic 实例,并且您应该使用 data User = ... deriving (Generic) 自动派生它,这又需要:

{-# LANGUAGE DeriveGeneric #-}

每当您将字符串文字 "whatever" 用作 String 以外的其他内容时,都需要 OverloadedStrings 扩展名。在Servant教程中,写的时候最先出现这个:

{-# LANGUAGE OverloadedStrings #-}

instance Accept HTMLLucid where
    contentType _ = "text" // "html" /: ("charset", "utf-8")

此处,///: 运算符期望使用 ByteString 类型:

(//) :: ByteString -> ByteString -> MediaType
(//) :: MediaType -> (ByteString, ByteString) -> MediaType

如果没有 OverloadedStrings 扩展,您需要提供从 String 文字到 ByteString 类型的显式转换:

import qualified Data.ByteString.Char8 as C

instance Accept HTMLLucid where
    contentType _ = C.pack "text" // C.pack "html" /: (C.pack "charset", C.pack "utf-8")

接下来是 HTMLLucidMimeRender 实例所需的 MultiParamTypeClassesFlexibleInstances

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}

instance ToHtml a => MimeRender HTMLLucid a where
    mimeRender _ = renderBS . toHtml

MultiParamTypeClasses 扩展是一个常用的扩展,每当您尝试定义采用多个参数的 classinstance(或使用 class 约束)时都需要. MimeRender class 实际上有两个参数。第一个是接受的 MIME 类型的类型级标记,此处为 HTMLLucid。第二个是将由实例呈现为该 MIME 类型内容的类型。因为这是一个双参数 class,您需要 MultiParamTypeClasses 扩展来为它编写实例。

此外,在标准 Haskell 中,您只能为 SomeType var1 var2 var3 形式的参数编写实例(可能有零个变量)。因此,您可以编写一个特定实例,其中第一个参数的形式为 SomeType,第二个参数的形式相同:

instance MimeRender HTMLLucid Int where ...

甚至第二个参数的形式为 SomeType var1:

instance MimeRender HTMLLucid (Maybe var) where ...

但是第二个参数不能是普通变量 a,除非您启用 FlexibleInstances.

因此,列表:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}

涵盖服务器教程所需的大部分内容。

据我所知,RankNTypes只需要写:

type (~>) m n = forall a. m a -> n a

这仅用于在介绍 hoistServer 之前说明一般概念,实际上并不需要用于其他任何事情。我也没有看到服务器教程中的任何地方都需要 ScopedTypeVariablesGeneralizedNewtypeDeriving