【问题标题】:How to identify required extensions for Servant如何识别 Servant 所需的扩展
【发布时间】:2020-07-31 14:07:43
【问题描述】:

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

【问题讨论】:

  • 您可以只编写代码并在需要时添加扩展 - 大多数情况下,GHC 会在编译失败时告诉您需要什么扩展。快速浏览了一下,TypeOperators 对于使用 Servant 显然是必不可少的,其他大多数可能需要也可能不需要,这取决于您还做什么。
  • 再看一遍,我记得DataKinds 也是必不可少的(我以为是,但一开始不记得为什么),对于您的 API 类型中需要的所有类型级别列表。

标签: haskell servant


【解决方案1】:

根据@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")

接下来是MultiParamTypeClassesFlexibleInstances,这是MimeRender 实例对HTMLLucid 的要求:

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

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

MultiParamTypeClasses 扩展是一个常用的扩展,每当您尝试定义采用多个参数的classinstance(或使用类约束)时,都需要使用该扩展。 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之前仅用于说明一般概念,实际上并不需要其他任何东西。我也没有看到服务器教程中的任何地方都需要ScopedTypeVariablesGeneralizedNewtypeDeriving

【讨论】:

  • 谢谢!没想到这么详细的回答。您提到编译器可以在 90% 的情况下建议正确的扩展。 10% 是什么?
【解决方案2】:

我不知道有没有比“根据编译器的抱怨禁用一切并一一启用”更好的方法

【讨论】:

  • 谢谢!我能够使用这种方法减少扩展的数量。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多