跳到内容

最近一次提交

 

历史

历史
692行(525个位置)·17.1 KB

哈斯克尔风格.md

文件元数据和控件

692行(525个位置)·17.1 KB

这份文件被无耻地抄袭自约翰·蒂贝尔[https://github.com/tibbe/haskell-style-guide网站],然后扩展到适合我的需要。

Haskell风格指南

这是一个简短的文档,描述了这个项目。我试图涵盖格式和命名。当本指南未涵盖某些内容时,您应该留下来与其他模块中的代码一致。

某些部分标有建议,这意味着我在这里没有强烈的意见。有些观点只适用于Agda项目。

良好代码质量的一般准则

  • 编写代码时要考虑验证。

    • 为每个函数、数据类型和构造函数编写一个目的语句。

    • 状态不变量、前置和后置条件。

    如果代码不适合表达其属性,这意味着它的结构不够好或模块化不够好。

  • 将代码结构为功能性较小的组件可以独立理解。

格式化

线条长度

通常,最大行长度应为80个字符。如果您不时将此扩展到100个字符,则无需抱怨。超过100个字符的例外是可能的,例如,对于进行调试打印的行。

缩进

标签是非法的。使用空格进行缩进。缩进代码块具有2空间。建议:缩进哪里关键字两个空格设置除了代码的其余部分,并在哪里第2条空格。一些示例:

说你好 :: IO(输入输出) ()说你好= 名称<- 获取线路
    输入StrLn $问候语名称哪里问候语名称= "你好," ++名称++ "!"

滤波器 ::( -> 布尔)->[]->[]滤波器_[]     = []
滤波器p(x:X轴)|第x页=x个: 滤波器p xs(p xs)| 否则 = 滤波器p xs(p xs)

观察Pfenning原理.压痕应稳定标识符重命名(字母-质量)。这意味着在额外缩进之前换行。

以下压痕方案在长度变化下不稳定重命名任何滤波器,第页,x个,X轴.

滤波器 ::( -> 布尔)->[]->[]滤波器_[]                 = []
滤波器p(x:X轴)|第x页=x个: 滤波器p xs(p xs)| 否则 = 滤波器p xs(p xs)

建议:空白行

顶级定义之间有一行空白。如果定义是long和/或本身包含空行,两个空行也是好的。类型签名和函数定义之间没有空行。在类型类实例中的函数之间添加一个空行如果函数体较大,则声明。运用你的判断力。

空白

用单边的空格包围二进制运算符。使用你对在算术周围插入空格的更好判断操作符,但始终保持两边空白的一致性二进制运算符。在lambda后面插入空格。

数据声明

对齐数据类型定义中的构造函数。对齐分隔垂直条|使用=.将haddockumentation添加到类型(前缀)和每个构造函数(后缀)。构造函数文档应在以下行中。很短的文档可以直接放在构造函数之后(参见记录)。例子:

-- |严格的二叉树。
数据  
  = 分支机构 ! !( )!( )-- ^标记的二进制节点。|叶子
      -- ^空树。
  衍生(等式,订单)

记录格式如下:

-- |简单配对者使用的个人数据。
数据  = 
  { 名字 :: !字符串  -- ^名字。,姓氏  :: !字符串  -- ^姓氏。,年龄       :: !国际     -- ^年龄。
  } 衍生(等式,显示)

记录每个字段。以点结束文档。

列出声明

对齐列表中的元素,将逗号放在前面。例子:

例外=[无效状态代码,任务内容标题,内部服务器错误]

建议:Pragmas

将杂注紧跟在它们应用到的函数之后。例子:

身份证件 ::  -> 
身份证件x个=x个{-#内联标识#-}

对于数据类型定义,必须将杂注放在应用于的类型。例如:

数据 阵列 e(电子) = 阵列
    {-#取消打包#-}!国际
    !二进制数组

悬挂式Lambdas

您可以缩进或不缩进“挂起”lambda后面的代码。使用你的判断。一些示例:

酒吧 :: IO(输入输出) ()酒吧= 对于M_[1,2,]$ \n个-> 
    输入StrLn "数字来了!"
    打印n个foo公司 :: IO(输入输出) ()foo公司= 阿洛卡牌手表10 $ \->阿洛卡牌手表20 $ \b条->c函数a b

导出列表

按如下方式设置导出列表的格式:

模块 数据。设置(--*@Set@类型
    设置,空的,单子

    --*正在查询,成员)哪里

If-then-else条款(不强制执行)

一般来说,防守和模式匹配应该优先于if-then-else条款,如有可能。短箱通常应放在单行中(当线路长度允许时)。

编写非单数代码时(即不使用)和警卫并且不能使用模式匹配,则可以对齐if-then-else子句你喜欢正常表达式:

foo公司=
  如果 ...
  然后 ...
  其他的 ...

否则,应符合2空格缩进规则然后其他的关键字应该对齐。示例:

foo公司= some代码如果条件然后其他代码其他的一些替代代码
foo公司=酒吧$ \库克斯-> 如果谓词qux然后做一些愚蠢的事其他的其他代码

相同的规则适用于嵌套do块:

foo公司= 指令<-解码指令跳过<-负载记忆。跳过如果跳过== 0x0000个
    然后 执行指令添加周期$指令循环指令其他的 商店记忆。跳过0x0000个添加周期1

Case表达式

case表达式中的替代项可以使用以下任一项缩进以下两种样式:

foobar公司= 案例某物属于
  只是j->foo公司没有什么 ->酒吧

或作为

foobar公司=
  案例某物属于
    只是j->foo公司没有什么 ->酒吧

对齐->有助于可读性的箭头。

进口

进口应按以下顺序分组:

  1. 标准库导入
  2. 相关第三方进口
  3. 特定于本地应用程序/库的导入

在每组导入之间放置一个空行。每个中的导入组应该按模块名称的字母顺序排序。

典型的模块标题如下所示。

--语言杂注{-#语言灵活实例#-}{-#语言类型同义词实例#-}{- |模块haddockumentation。

本模块演示了一些标准实践。
 -}

--控制.*导入

进口 控制。适用 躲藏(空的)进口 控制。莫纳德。阅读器

--Data.*导入

进口 数据。可折叠的 作为 折叠
进口 数据。列表 作为 列表
进口 数据。地图(地图)进口 有资格的 数据。地图 作为 地图
进口 数据。也许 吧
进口 数据。顺序(顺序)进口 有资格的 数据。顺序 作为 顺序
进口 数据。设置(设置)进口 有资格的 数据。设置 作为 设置
进口 有资格的 数据。可穿越的 作为 Trav公司

--其他外部进口

进口 测试。快速检查

--Agda成组进口

进口 阿格达。互动。选项

进口 有资格的 阿格达。语法。摘要 作为 A类
进口 阿格达。语法。通用 作为 通用
进口 有资格的 阿格达。语法。混凝土 作为 C类
进口 有资格的 阿格达。语法。混凝土。姓名 作为 C类
进口 阿格达。语法。内部 作为 

进口 阿格达。类型检查。莫纳德 作为 中医科
进口 阿格达。类型检查。减少
进口 阿格达。类型检查。替代

--最后:阿格达。实用程序*

进口 阿格达。实用程序。透镜
进口 阿格达。实用程序。也许 吧
进口 阿格达。实用程序。无效的
进口 阿格达。实用程序。薄纱

--最后一名:阿格达。实用程序。不可能的

#包括../../未定义。小时进口 阿格达。实用程序。不可能的

上述示例中给出的所有短模块名称都是必需的:地图,设置,顺序(这些也应该单独导入类型不合格,避免出现以下情况地图。地图),折叠,Trav公司、和Agda特异性通用,C类,A类,用于语法和中医科对于排字单子。

构建模块内容

首先介绍模块的主要功能。

将辅助功能分成合理的组。开始一个主题标题如下:

------------------------------------------------------------------------
--*位置上的单体结构。
------------------------------------------------------------------------

这意味着72个破折号,黑线鳕鱼航向,再加上72个破折号。哈多克也可以使用副标题。

把无聊的实例放在文件的最后。(比如KillRange公司实例。)

评论

标点符号

写适当的句子;以大写字母开头,并适当使用标点符号。

顶级定义

注释每个顶级函数(尤其是导出的函数),并提供类型签名;在注释中使用Haddock语法。对每个导出的数据类型进行注释。函数示例:

-- |在套接字上发送消息。插座必须处于连接状态
--州。返回发送的字节数。应用程序包括
--负责确保所有数据都已发送。
发送 :: 插座      -- ^已连接的插座。
     -> 字节字符串  -- ^要发送的数据。
     -> IO(输入输出) 国际      -- ^发送的字节数。

对于较长的函数名称,请在::以避免深度压痕。

-- |在套接字上发送消息。插座必须处于连接状态
--州。返回发送的字节数。应用程序包括
--负责确保所有数据都已发送。在A插座上发送A消息:: 插座      -- ^已连接的插座。
  -> 字节字符串  -- ^要发送的数据。
  -> IO(输入输出) 国际      -- ^发送的字节数。

对于功能,文档应提供足够的信息在不查看函数定义的情况下应用函数。

记录示例:

-- |布拉布拉布拉。
数据  = 
    { 年龄  :: !国际     -- ^年龄。,名称 :: !字符串  -- ^名字。
    }

对于需要更长注释的字段,请按如下格式设置:

数据 记录 = 记录
  { 字段1 :: !文本
      -- ^这是一条很长很长的评论,被分开了
      --多行。,字段2 :: !国际
      -- ^这是第二条很长很长的评论,被拆分了
      --在多条线路上。
  }

最后的评论

用2个空格将行尾注释与代码分隔开。排列数据类型定义的注释。一些示例:

数据 分析器 = 分析器
    !国际         --当前位置。
    !字节字符串  --剩余输入。

foo公司 :: 国际 -> 国际福恩=* 32 + 9
  哪里= 453645243  --神奇的杂烩盐。

建议:链接

经济地使用内联链路。鼓励您为添加链接API名称。没有必要为Haddock注释。因此,我们建议为API名称添加链接如果:

  • 用户可能真的想点击它以获得更多信息(在您的判断),以及

  • 仅适用于注释中每个API名称的第一次出现(不要费心重复链接)

在下面的示例中,函数的用户可能需要调用使正常化,因此链接到的定义使正常化添加了:

-- |前提条件:水平“正常化”。
等于级别' :: 对于所有人 . Monad转换  => 水平 -> 水平 ->  ()

命名

使用驼峰箱(例如。函数名称)命名函数和上限时骆驼箱(例如。数据类型)命名数据类型时。

出于可读性的原因,在使用缩写。例如,写Http服务器而不是HTTPS服务器例外:两个字母的缩写,例如。IO(输入输出).

模块

命名模块时使用单数,例如使用数据。地图数据。ByteString.内部而不是数据。地图数据。字节字符串.内部.

样式

记录而不是元组!

元组仅用于一次性数据结构,如返回类型本地函数的。对于有意义的组合数据,使用记录!

而不是:

-- |模块:顶级杂注加上其他顶级声明。
类型 模块 =([布拉格], [宣言])

最好写:

-- |模块:顶级杂注和其他顶级声明。
数据 模块 = 模块
  { 模块碎片 ::[布拉格],模块Decls   ::[宣言]}

数据类型而不是布尔,也许 吧,要么...

使用带有有意义的构造函数名称的手动数据类型而不是用布尔,也许 吧,要么等。

错误的示例(分析器生成器文件):

灯绑定荒谬 ::{要么[要么 躲藏 LamBinding公司] [Expr公司] }灯绑定荒谬
  : 域自由绑定 兰宾兹{左侧 $ 地图 赖特 $1 ++ $2}| 类型绑定 兰宾兹{左侧 $ 赖特(域已满 $1): $2}| 域自由绑定荒谬{案例 $1 属于
                                    左侧-> 左侧 $ 地图 赖特赖特-> 赖特电子}| 类型绑定{左侧[赖特 $ 域已满 $1] }| '(' ')'{左侧[左侧 未隐藏] }| '{' '}'{左侧[左侧 隐藏] }

新类型而不是类型同义词!

使用新型为类型同义词提供一些语义,允许自定义打印等。

例子:

-- |顶级模块名称。与文件系统一起使用。
--
--不变量:列表不能为空。

新型 顶层模块名称
  = 顶层模块名称 { 模块名称部件 ::[字符串]}
  衍生(显示,等式,订单,可打字的)

无点风格

避免过度使用无点风格。例如,这很难阅读:

--错误:(f)=(克.).小时

使用应用程序运算符$减少括号,尤其是长距离的。

一元编程

循环

使用地图M地图M_当环体较短时。使用对于M对于M_用于大型回路体。

错误:

构造函数应用程序 ::[(一、。精氨酸 期限,一、。Dom公司 类型)]-> 中医科(也许 吧[国家])构造函数应用程序参数= X轴<- 地图M(\(e,t)-> t吨<-减少(unDom t)构造函数应用程序(unArg e)(ignoreSharingType t)参数返回(凹面(concat) <$> 序列X轴)

良好:

构造函数应用程序 ::[(一、。精氨酸 期限,一、。Dom公司 类型)]-> 中医科(也许 吧[国家])构造函数应用程序参数= X轴<-对于M参数$ \(e,t)-> t吨<-减少$unDom t(取消域名)构造函数应用程序(unArg e)$忽略共享类型t返回 $ 凹面(concat) <$> 序列X轴

一元计算案例

在中使用额外的一元组合符阿格达。实用程序。莫纳德,阿格达。实用程序。也许 吧等。

调用编译器:: 文件路径  -- ^编译器的路径。
  ->[字符串]-- ^命令行参数。
  -> 中医科 ()callCompiler命令参数= 镜子<-callCompiler的cmd参数案例镜子属于
    没有什么     -> 返回 ()
    只是错误->类型错误(编译错误错误)

与其说绑定之后是琐碎的情况,使用的版本何时对于也许 吧类型:

callCompiler命令参数= whenJustM(调用编译器的cmd参数)$ \错误-> 类型错误$ 编译错误错误

保存易失性绑定镜子而且很无聊返回().请注意,进一步的eta压缩将删除绑定错误,然而,这个绑定实际上有助于读取代码。

callCompiler命令参数= whenJustM(调用编译器的cmd参数)$类型错误. 编译错误

建议:应对懒惰

默认情况下,使用严格的数据类型和惰性函数。

数据类型

构造函数字段应该严格,除非有明确的原因让他们变得懒惰。这样可以避免因过多操作而导致的许多常见陷阱懒惰和减少程序员必须的大脑周期数花时间思考评估顺序。

--很好
数据  = 
  { 点X :: !双精度  -- ^X坐标,点Y :: !双精度  -- ^Y坐标
  }
--
数据  = 
  { 点X :: 双精度  -- ^X坐标,点Y :: 双精度  -- ^Y坐标
  }

此外,解压缩简单字段通常可以提高性能和减少内存使用:

数据  = 
  { 点X ::{-#打开#-}!双精度  -- ^X坐标,点Y ::{-#打开#-}!双精度  -- ^Y坐标
  }

作为替代打开布拉格,你可以把

{-#选项_GHC-funbox-strict字段#-}

在文件的顶部。而是在文件inself中包含此标志例如在Cabal文件中的是优选的,因为优化将是即使有人使用其他方法编译文件(即优化附加到它所属的源代码中)。

请注意-funbox-strict字段适用于所有严格字段,而不是只有小字段(例如。双精度国际). 如果您使用GHC 7.4或稍后您可以使用NOUNPACK餐厅有选择地选择拆包由启用-funbox严格字段.

功能

让函数参数变懒,除非您明确需要严格。

需要严格函数参数的最常见情况是带累加器的递归:

糠醛 ::[国际]-> 国际糠醛=0
  哪里!符合[]    =符合去acc(x:X轴)=去(acc+x) x轴

其他

警告

Agda使用GHC警告的子集作为错误。请参阅Agda.cabal。