使用F#等函数语言的函数的参数名称

时间:2013-02-26 11:53:28

标签: haskell f# functional-programming ocaml named-parameters

我读过一本名为Clean code的书。我从本书中获得的最强烈的信息之一是代码必须可读。我不明白为什么像F#这样的函数式语言不会在intellisense或类型定义中包含函数参数名称?

比较

val copy: string -> string -> unit

val copy: sourceFile:string -> destinationFile:string -> unit

功能世界的最佳实践是什么?我们更喜欢单参数功能吗?这是Clean code推广的内容。或者我们是否应该将记录用于2+参数的所有功能?

我知道一个解决方法是使用static member而不是let函数,但这不是一种功能性方法,是吗?

编辑:

只是提供更多信息:

  • HaskelladdThree :: Int -> Int -> Int -> Int
  • OCamlUnix.unlink: (string -> unit)

肯定是其他人。它们只是不在类型定义中显示参数名称。

4 个答案:

答案 0 :(得分:7)

如果你练习有类型的编程,这就是可以在类型系统中静态反映程序的许多语义内容的想法,你会发现很多(但不是全部)情况,命名参数对于可读性不是必需的。

考虑OCaml的List标准库中的以下示例。通过知道它们在列表上运行,并且(希望清楚:我们都是好名字选择)函数的名称,你会发现你不需要解释参数的作用。

val append : α list -> α list
val flatten : α list list -> α list
val exists: (α -> α bool) -> α list -> bool
val map: (α -> β) -> α list -> β list
val combine : α list -> β list -> (α * β) list

请注意,最后一个示例很有趣,因为它不清楚代码将执行的操作。事实上会有几种解释。 combine [1;2] [3;4]会返回[(1,3); (2,4)],而不会返回[(1,3); (1,4); (2,3); (2,4)]。当两个列表的长度不同(故障模式不清楚)时,还不清楚会发生什么。

total 的函数可能引发异常或不终止,通常需要更多关于失败案例及其行为方式的文档。这是支持我们称之为纯编程的一个强有力的论据,其中函数的所有行为都表示为返回(没有异常,可观察到的状态变异) ,或非终止),因此可以由类型系统静态捕获。

当然,这只适用于参数的功能;他们的类型很清楚,他们做了什么。这不是所有函数的情况,例如考虑blit模块的String函数(我确定你最喜欢的语言也有这样的例子):

val blit : string -> int -> string -> int -> int -> unit

啊?

由于这个原因,编程语言添加了支持命名参数。例如,在OCaml中,我们有“标签”,允许命名参数。相同的函数在StringLabels模块中导出为:

val blit : src:string -> src_pos:int -> dst:string -> dst_pos:int -> len:int -> unit

那更好。是的,在某些情况下,命名参数很有用。

但请注意,命名参数可用于隐藏错误的API设计(也许上面的示例也是针对这种批评的目标)。考虑:

val add : float -> float -> float -> float -> float -> float -> float * float * float
晦涩难懂,是吧?但那时:

type coord = {x:float; y:float; z:float}
val add : coord -> coord -> coord

那好多了,我不需要任何参数标记(可以说是唱片标签提供命名,但事实上我在这里同样可以使用float * float * float;值记录可以包含命名(和选项?)这一事实参数也是一个有趣的评论。)

David M. Barbour develops the argument命名参数是语言设计的“拐杖”,用于篡改API设计者的懒惰,并且没有它们鼓励更好的设计。我不确定我是否同意在所有情况下都可以有利地避免命名参数,但他肯定有一点同意我在帖子开头的有类型的宣传。通过提高抽象级别(通过更多的多态/参数类型或更好的问题域抽象),您会发现减少了对参数命名的需求。

答案 1 :(得分:6)

  

或者我完全错了,它与功能和命令式编程之间的差异无关?

你并非完全错误,因为具有HM类型推断的函数式语言通常根本不需要类型注释,或者至少不需要在任何地方。

除此之外,类型表达式不一定是函数类型,因此“参数名称”的概念不适用。总而言之,一个名称只是冗余,它不会向类型添加任何信息,这可能是不允许它的原因。

相反,在命令式语言中,类型推断最近几乎是未知的。因此,您必须声明所有内容(使用静态类型语言),因此名称和类型往往出现在一个地方。而且,由于函数不是第一类的,所以函数类型的概念,更不用说描述函数类型的表达式,仅仅是未知的。

观察到最近的发展(如“lambda”语法等),其类型已知或易于推断的论证的概念也出现在这些语言中。当我没记错的时候,甚至有句法缓和来避免长名称,lambda论证只是甚至 _

答案 2 :(得分:5)

Haskell的类型同义词可以帮助使类型签名更加自我记录。考虑例如函数writeFile,它只是将字符串写入文件。它有两个参数:要写入的字符串和要写入的文件名。或者是相反的方式?这两个参数都是String类型,因此要判断哪个是哪个参数并不容易!

但是,当您查看the documentation时,您会看到以下类型签名:

writeFile :: FilePath -> String -> IO ()

这清楚地表明(对我来说,至少!)该函数是如何使用的。现在,由于FilePath只是String的同义词,因此没有什么能阻止您像这样使用它:

writeFile "The quick brown fox jumped over the lazy dog" "test.txt"

但是如果你在IDE中得到类型FilePath -> String -> IO ()作为提示,我认为这至少是朝着正确方向发展的一大推力!

您甚至可以更进一步为文件路径制作newtype,这样您就不会意外地混淆文件名和内容,但我想这会增加比它的价值更麻烦,而且可能还有历史原因这没用。

答案 3 :(得分:3)

  

功能世界的最佳实践是什么?

OCaml有另一种名为Core的标准库。它几乎到处都使用labeled个参数。例如

val fold_left : 'a t -> init:'b -> f:('b -> 'a -> 'b) -> 'b

P.S。我没有关于其他功能语言的信息。