Haskell 如何识别服务所需的扩展
我正在阅读一篇文章,但不明白代码的哪些部分使用了哪个扩展。本教程首先在文件开头添加10个扩展名,但第一个示例只需要3个扩展名。当我在Servant中实现自己的服务器时,我希望使用最少数量的扩展。是否有一种方法可以识别所需的最小扩展?我不知道是否有比“根据编译器的抱怨禁用所有东西并逐个启用它们”更好的方法。根据@GeorgeLyubenov的回答,完全可以让编译器告诉您需要什么。80-90%的情况下,编译器错误消息会建议进行扩展,如果这样可以解决问题的话。有一些情况下,它无法找出它,你只需要学习如何识别这些。当我在编写代码时,我常常会添加超出需要的扩展,因为我会尝试一些东西,然后放弃它们。最后,如果我想精简扩展集,我只需尝试一个接一个地删除它们,看看编译器是否会抱怨(这与George的方法相反)。这对于包含大量推荐扩展的教程或软件包非常有用,因为您可以将它们全部包括在内,然后尝试逐个删除。它有助于使用IDE或编辑器模式(我使用EmacsHaskell 如何识别服务所需的扩展,haskell,servant,Haskell,Servant,我正在阅读一篇文章,但不明白代码的哪些部分使用了哪个扩展。本教程首先在文件开头添加10个扩展名,但第一个示例只需要3个扩展名。当我在Servant中实现自己的服务器时,我希望使用最少数量的扩展。是否有一种方法可以识别所需的最小扩展?我不知道是否有比“根据编译器的抱怨禁用所有东西并逐个启用它们”更好的方法。根据@GeorgeLyubenov的回答,完全可以让编译器告诉您需要什么。80-90%的情况下,编译器错误消息会建议进行扩展,如果这样可以解决问题的话。有一些情况下,它无法找出它,你只需要学习如
dante
),可以快速键入检查文件(例如,每次保存时),因此您不必手动运行GHC 20次
据我所知,没有任何编译器标志可以转储所使用的扩展列表,因此,尝试和错误方法是您所能做的最好方法
…除非你真的想试着理解扩展的含义。这并不是说它们可以毫无理由地启用编译器代码的随机位。它们提供了文档化的、可理解的特性,虽然您可能无法理解和记住所有特性,但理解其中的大部分并不困难
在Servant教程中给出的列表中:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
以下两个是编写服务API的关键:
type UserAPI1 = "users" :> Get '[JSON] [User]
最容易理解的是TypeOperators
,这就是让我们在API类型中使用中缀运算符,如:>
。如果没有它,您需要将API编写为:
type UserAPI1 = (:>) "users" (Get '[JSON] [User])
这在很大程度上违背了一开始就有一个好的基于运算符的语法的目的。第二个关键的扩展名,数据种类
,有点难理解,但它允许您使用值作为类型,比如字符串“users”
(和“勾选列表”“[…]”
,但不是未勾选的列表[User]
或JSON
类型本身)
因此,任何指定API的服务程序几乎肯定需要:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
如果你曾经写过。。。派生(通用)
,您需要DeriveGeneric
。在服务程序中,如果您希望使用从您的数据类型自动派生的ToJSON
实例来提供JSON,则最有可能出现这种情况。对于教程中的用户
数据类型,实例:
instance ToJson User
要求User
有一个Generic
实例,您需要使用datauser=。。。派生(通用)
,这反过来需要:
{-# LANGUAGE DeriveGeneric #-}
当您将字符串文字“whatever”
用作字符串以外的内容时,需要使用重载字符串
扩展名。在Servant教程中,在编写以下内容时首先出现:
{-# LANGUAGE OverloadedStrings #-}
instance Accept HTMLLucid where
contentType _ = "text" // "html" /: ("charset", "utf-8")
在这里,/
和/:
操作员希望使用ByteString
类型:
(//) :: ByteString -> ByteString -> MediaType
(//) :: MediaType -> (ByteString, ByteString) -> MediaType
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")
如果没有OverloadedStrings
扩展,您需要提供从String
文本到ByteString
类型的显式转换:
(//) :: ByteString -> ByteString -> MediaType
(//) :: MediaType -> (ByteString, ByteString) -> MediaType
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")
接下来是htmlucid
的MimeRender
实例所需的multiparamtypeClass
和FlexibleInstances
:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
instance ToHtml a => MimeRender HTMLLucid a where
mimeRender _ = renderBS . toHtml
当您试图定义具有多个参数的类
或实例
(或使用类约束)时,通常需要使用multiparamtypeclass
扩展。MimeRender
类实际上包含两个参数。第一个是可接受MIME类型的类型级标记,这里是htmlucid
。第二个是实例将呈现给该MIME类型内容的类型。因为这是一个双参数类,所以需要使用multiparamtypeclass
扩展为其编写实例
此外,在标准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
在介绍wingserver
之前,它仅用于说明一般概念,实际上不需要用于其他任何内容。我也不认为服务器教程中的任何地方都需要ScopedTypeVariables
或GeneralizedNewtypeDeriving
。您只需编写代码并在需要时添加一个扩展,对于大多数情况,GHC会在编译失败时告诉您需要什么扩展。快速浏览后,TypeOperators
显然是使用Servant的必要条件,其他大多数可能需要,也可能不需要,这取决于您还做了什么。再次浏览后,我记得datacates
也很重要(我认为