6

という本を読みましたClean code。この本から得た最も強いメッセージの 1 つは、コードは読み取り可能でなければならないということです。F# などの関数型言語が関数パラメーター名をインテリセンスまたは型定義に含めない理由がわかりません。

比較

val copy: string -> string -> unit

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

機能の世界でのベストプラクティスは何ですか? 単一パラメーター関数を優先しますか? これがClean code促進するものです。それとも、2 つ以上のパラメーターのすべての関数にレコードを使用しますか?

static member関数の代わりに使用することが 1 つの回避策であることは知っていletますが、これは関数的なアプローチではありませんね。

編集:

詳細情報を提供するだけです:

  • ハスケル:addThree :: Int -> Int -> Int -> Int
  • OCaml :Unix.unlink: (string -> unit)

そして確かに他の人。型定義にパラメーター名が表示されないだけです。

4

4 に答える 4

7

タイプフル プログラミング(プログラムのセマンティック コンテンツの多くを型システムに静的に反映できるという考え方) を実践すると、多くの場合 (すべてではありません)、読みやすさのために名前付き引数が必要ないことがわかります。

ListOCamlの標準ライブラリにある次の例を考えてみましょう。それらがリストで動作することを知っていて、関数の名前 (うまくいけば明確です: 私たちは皆、適切な名前の選択を望んでいます) を使用すると、引数が何をするかについて説明する必要がないことがわかります。

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]返します。また、2 つのリストの長さが同じでない場合に何が起こるかも明確ではありません (障害モードは不明です)。[(1,3); (2,4)][(1,3); (1,4); (2,3); (2,4)]

例外が発生したり終了しない可能性のあるtotalではない関数は、通常、失敗のケースとは何か、およびそれらがどのように動作するかについて、より多くのドキュメントを必要とします。これは、私たちが純粋プログラミングと呼ぶものを支持する強力な議論の 1 つであり、そこでは関数のすべての動作がを返すという観点で表現され(例外なし、観察可能な状態の突然変異、または非終了)、したがって、によって静的にキャプチャできます。型システム。

もちろん、これは十分にパラメトリックな関数に対してのみうまく機能します。彼らは何をするのかを非常に明確にするタイプを持っています。これはすべての関数に当てはまるわけではありません。たとえば、モジュールの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は、名前付きパラメーターは言語設計の「松葉杖」であり、API 設計者の怠惰を改ざんするために使用され、それらを持たないことがより良い設計を促進するという議論を展開しています。すべての状況で名前付きパラメーターを回避して有益であることに同意するかどうかはわかりませんが、彼は確かに、私の投稿の冒頭にあるタイプフル プロパガンダに同意する点を持っています。抽象化のレベルを上げることで (よりポリモーフィック/パラメトリックな型またはより優れた問題領域の抽象化を通じて)、パラメーターの命名の必要性が減少することがわかります。

于 2013-02-27T13:46:15.850 に答える
6

それとも私は完全に間違っていて、関数型プログラミングと命令型プログラミングの違いとは何の関係もありませんか?

HM 型推論を使用する関数型言語が型注釈をまったく必要としないことが多い限り、または少なくともどこでも必要としない限り、あなたは完全に間違っているわけではありません。

これに加えて、型式は必ずしも関数型ではないため、「パラメーター名」の概念は適用されません。全体として、そこにある名前は冗長であり、typeに情報を追加しないため、それが許可されない理由になる可能性があります。

逆に、命令型言語では、最近の型推論はほとんど知られていませんでした。したがって、(静的に型付けされた言語では) すべてを宣言する必要があるため、名前と型が 1 つの場所に表示される傾向があります。さらに、関数は第一級市民ではないため、関数型の概念はもちろん、関数型を記述する式も不明です。

最近の開発 (「ラムダ」構文など) では、型が既にわかっている、または簡単に推測できる引数の概念がこれらの言語にも現れることに注意してください。そして、私の記憶が正しければ、長い名前を避けるための構文上の緩和さえあります。ラムダ引数はそれまたは_です。

于 2013-02-26T14:04:30.193 に答える
6

Haskell の型シノニムは、型シグネチャをより自己文書化するのに役立ちます。たとえば、writeFile文字列をファイルに書き込むだけの関数 を考えてみましょう。書き込む文字列と書き込むファイル名の 2 つのパラメーターがあります。それとも逆でしたか?どちらのパラメータも typeStringであるため、どれがどれであるかを簡単に区別することはできません!

ただし、ドキュメントを見ると、次の型シグネチャが表示されます。

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

これにより、関数がどのように使用されることを意図しているかが (少なくとも私には!) 明確になります。さて、FilePathは の同義語でStringあるため、次のように使用することを妨げるものは何もありません。

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

しかし、IDE で型をヒントとして取得できればFilePath -> String -> IO ()、少なくとも正しい方向への大きな前進だと思います!

さらに一歩進んでnewtypefor filepaths を作成して、誤ってファイル名とコンテンツを混同しないようにすることもできますが、それは価値がある以上に手間がかかると思います。おそらく、これが行われない歴史的な理由もあります.

于 2013-02-27T14:21:34.293 に答える
4

機能の世界でのベストプラクティスは何ですか?

Coreと呼ばれるOCamlの代替標準ライブラリがあります。ほぼすべての場所でラベル付きパラメーターを使用します。例えば

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

PS 他の関数型言語に関する情報はありません。

于 2013-02-26T13:01:09.537 に答える