【问题标题】:Parameter names of a function using a functional language such as F#使用函数式语言(如 F#)的函数的参数名称
【发布时间】:2013-02-11 20:45:03
【问题描述】:

我读过一本叫Clean code 的书。我从书中得到的最强烈的信息之一是代码必须可读。我不明白为什么 F# 等函数式语言不将函数参数名称包含在智能感知或类型定义中?

比较

val copy: string -> string -> unit

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

函数式世界中的最佳实践是什么?我们应该更喜欢单参数函数吗?这就是Clean code 所提倡的。或者我们应该为 2+ 参数的所有函数使用 records 吗?

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

编辑:

只是为了提供更多信息:

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

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

【问题讨论】:

  • 您使用的是什么版本的语言?在 F# 3.0 中,在 F# interactive 中键入 let f x y = x + y + 1 会得到:val f : x:int -> y:int -> int(而 IntelliSense 在编辑器中会得到相同的结果)。
  • 这是对的,但是如何:让 g() = let f x y = x + y + 1 in ()。任何不在顶层的东西都不起作用。但很高兴知道 F# 团队试图对此做些什么;)我只是对函数式编程的一般性质感到困惑。或者其他函数式语言是否显示参数名称?
  • 这是关于 IDE 和开发环境,而不是语言。
  • 至少在 Haskell 中,模式匹配使得类型签名中的命名参数通常不切实际。 foo Nothing = bar; foo (Just []) = baz; foo (Just (x:xs)) = quux。 IDE 应该怎么做?另外,foldl' :: (a -> b -> a) -> a -> [b] -> a; foldl' f z = go z where go ...,定义中只明确提供了一些参数。
  • "我们更喜欢单参数函数吗?" 每个 函数只接受一个参数。 addThree :: Int -> Int -> Int -> Int 是一个接受Int 并返回的函数(一个接受Int 并返回的函数(一个接受Int 并返回Int 的函数))。 [当然这很麻烦,所以人们通常说一个函数接受三个参数,但有时,区别很重要。]

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


【解决方案1】:

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

考虑以下在 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)]。也不清楚当两个列表长度不同时会发生什么(故障模式不清楚)。

不是全部的函数,可能引发异常或不终止,通常需要更多关于失败案例是什么以及它们将如何表现的文档。这是支持我们所谓的纯编程的一个强有力的论据,其中函数的所有行为都以返回一个的形式表示(没有例外,可观察的状态突变,或非终止),因此可以被类型系统静态捕获。

当然,这只适用于足够参数的函数;他们有一种类型,可以非常清楚地说明他们的工作。并非所有函数都如此,例如考虑 String 模块的 blit 函数(我相信你最喜欢的语言也有这样的例子):

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 设计者的懒惰,并且没有它们会鼓励更好的设计。我不确定我是否同意在所有情况下都可以有利地避免命名参数,但他的观点肯定与我帖子开头的类型宣传一致。通过提高抽象级别(通过更多的多态/参数类型或更好的问题域抽象),您会发现您减少了对参数命名的需求。

【讨论】:

    【解决方案2】:

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

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

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

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

    请注意,随着最近的发展(如“lambda”语法等),类型已知或可以轻松推断的参数的概念也出现在这些语言中。如果我没记错的话,甚至还有语法上的缓和来避免长名称,lambda 参数只是 it 甚至 _

    【讨论】:

      【解决方案3】:

      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,这样您就不会意外混淆文件名和内容,但我想这增加了比它的价值更多的麻烦,而且这可能还有历史原因还没完成。

      【讨论】:

        【解决方案4】:

        函数式世界中的最佳实践是什么?

        OCaml 的替代标准库称为Core。它几乎在任何地方都使用labeled 参数。例如

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

        附:我没有关于其他函数式语言的信息。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-06-18
          • 1970-01-01
          • 1970-01-01
          • 2021-10-31
          • 1970-01-01
          • 1970-01-01
          • 2010-11-01
          • 1970-01-01
          相关资源
          最近更新 更多