根据@GeorgeLyubenov 的回答,完全可以让编译器告诉您您需要什么。在 80-90% 的情况下,编译器错误消息会建议扩展,如果这样可以解决您的问题。在某些情况下它无法弄清楚,您只需要学习如何识别它们即可。当我编写代码时,我经常会添加比我需要的更多的扩展,因为我尝试了一些事情然后放弃它们。最后,如果我想削减扩展集,我只是尝试一个一个地删除它们,看看编译器是否会抱怨(这与 George 的方法相反)。这对于具有大量推荐扩展名的教程或软件包非常有用,因为您可以将它们全部包含在内,然后尝试一个一个地删除它们。它有助于使用 IDE 或编辑器模式(我使用 Emacs dante)可以快速键入检查文件(例如,每次保存时),因此您不必手动运行 GHC 20 次。
据我所知,没有任何编译器标志可以转储使用的扩展列表,所以试错法是你能做的最好的方法......
...除非您真的想尝试理解扩展的含义。并不是说它们没有押韵或理由就启用了编译器代码的随机位。它们支持有据可查、易于理解的功能,虽然您可能无法理解和记住所有这些功能,但理解其中的大部分并不难。
Servant教程中给出的列表:
{-# 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")
接下来是MultiParamTypeClasses 和FlexibleInstances,这是MimeRender 实例对HTMLLucid 的要求:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
instance ToHtml a => MimeRender HTMLLucid a where
mimeRender _ = renderBS . toHtml
MultiParamTypeClasses 扩展是一个常用的扩展,每当您尝试定义采用多个参数的class 或instance(或使用类约束)时,都需要使用该扩展。 MimeRender 类实际上有两个参数。第一个是可接受的 MIME 类型的类型级别标记,此处为 HTMLLucid。第二个是将由实例呈现为该 MIME 类型的内容的类型。因为这是一个双参数类,所以您需要 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之前仅用于说明一般概念,实际上并不需要其他任何东西。我也没有看到服务器教程中的任何地方都需要ScopedTypeVariables 或GeneralizedNewtypeDeriving。