如何识别 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")
接下来是 HTMLLucid
的 MimeRender
实例所需的 MultiParamTypeClasses
和 FlexibleInstances
:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
instance ToHtml a => MimeRender HTMLLucid a where
mimeRender _ = renderBS . toHtml
MultiParamTypeClasses
扩展是一个常用的扩展,每当您尝试定义采用多个参数的 class
或 instance
(或使用 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
之前说明一般概念,实际上并不需要用于其他任何事情。我也没有看到服务器教程中的任何地方都需要 ScopedTypeVariables
或 GeneralizedNewtypeDeriving
。
我正在阅读 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")
接下来是 HTMLLucid
的 MimeRender
实例所需的 MultiParamTypeClasses
和 FlexibleInstances
:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
instance ToHtml a => MimeRender HTMLLucid a where
mimeRender _ = renderBS . toHtml
MultiParamTypeClasses
扩展是一个常用的扩展,每当您尝试定义采用多个参数的 class
或 instance
(或使用 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
之前说明一般概念,实际上并不需要用于其他任何事情。我也没有看到服务器教程中的任何地方都需要 ScopedTypeVariables
或 GeneralizedNewtypeDeriving
。