APIと関数型プログラミング


15

Clojureなどの関数型プログラミング言語への(明らかに限定された)経験から、データのカプセル化はそれほど重要ではないようです。通常、マップやセットなどのさまざまなネイティブタイプは、オブジェクトよりもデータを表すための優先通貨です。さらに、そのデータは一般に不変です。

たとえば、この件に関するインタビューで、Clojureの名声のRich Hickeyからの有名な引用の1つを次に示します

Fogus:その考えに従うと、Clojureがそのタイプでデータ隠蔽のカプセル化を行わないという事実に驚く人もいます。なぜデータ隠蔽をやめることにしたのですか?

ヒッキー:Clojureがプログラミングを抽象化に重点を置いていることを明確にしましょう。ただし、ある時点で、誰かがデータにアクセスする必要があります。また、「プライベート」という概念がある場合、それに対応する特権と信頼の概念が必要です。そして、それは非常に多くの複雑さと小さな価値を追加し、システムに硬直性をもたらし、しばしば物を本来あるべきでない場所に住まわせます。これは、単純な情報がクラスに入れられるときに発生する他の損失に追加されます。データが不変である限り、誰かが変更される可能性のあるものに依存するようになる可能性があることを除いて、アクセスを提供することによってもたらされる害はほとんどありません。まあ、大丈夫、人々は実際の生活の中で常にそうしていて、物事が変わると適応します。そして、それらが合理的であれば、彼らは、将来変化する可能性のある何かに基づいて決定を下すときを知っています。だから、それはリスク管理の決定であり、プログラマは自由にすべきだと思う。抽象化に向けてプログラミングを行い、実装の詳細と結婚することを警戒したいという感覚がなければ、優秀なプログラマーになることは決してありません。

オブジェクト指向の世界から来て、これは私が長年にわたって学んだ神聖な原則のいくつかを複雑にするようです。これらには、情報隠蔽、デメテルの法則、統一アクセス原則などが含まれます。カプセル化という一般的なスレッドにより、他の人が触れてはいけないことを知るためのAPIを定義できます。本質的に、一部のコードのメンテナーがコンシューマーのコードにバグを導入する可能性を心配することなく、自由に変更やリファクタリングを行えるようにするコントラクトを作成します(オープン/クローズ原則)。また、他のプログラマーがそのデータを取得または構築するために使用できるツールを知るための、クリーンで精選されたインターフェースを提供します。

データへの直接アクセスが許可されると、そのAPIコントラクトは壊れ、カプセル化の利点はすべてなくなるようです。また、厳密に不変のデータは、ドメイン固有の構造(オブジェクト、構造体、レコード)の受け渡しを、状態とその状態で実行できる一連のアクションを表すという意味であまり役に立たないようです。

APIを定義する必要があり、多くの開発者がシステムの特定の部分の操作に関与するなど、コードベースのサイズが巨大になったときに発生するように思われるこれらの問題に、機能コードベースはどのように対処しますか?これらのタイプのコードベースでこれがどのように処理されるかを示すこの状況の例はありますか?


2
オブジェクトの概念なしで正式なインターフェースを定義できます。それらを文書化するインターフェースの機能を作成するだけです。実装の詳細に関するドキュメントを提供しないでください。インターフェイスを作成しました。
Scara95

@ Scara95それは、インターフェイスのコードを実装し、消費者に何をすべきか、何をすべきでないかを警告するための十分なドキュメントを作成するために作業しなければならないという意味ではありませんか?コードが変更され、ドキュメントが古くなった場合はどうなりますか?私は通常、この理由から自己文書化コードを好みます。
ジェームズロック

とにかくインターフェースを文書化する必要があります。
Scara95

3
Also, strictly immutable data seems to make passing around domain-specific structures (objects, structs, records) much less useful in the sense of representing a state and the set of actions that can be performed on that state.あんまり。変更されるのは、変更が新しいオブジェクトに反映されることだけです。これは、コードについての推論に関しては大きな勝利です。可変オブジェクトを渡すことは、誰がそれらを変更する可能性があるかを追跡する必要があることを意味します。これは、コードのサイズに応じて拡大する問題です。
ドーバル

回答:


10

まず第一に、機能的に適切なもの、動的型付けとは何かに関するセバスチャンのコメントを2番目に取り上げます。より一般的には、Clojureは関数型言語とコミュニティのフレーバー1つであり、それに基づいてあまり一般化すべきではありません。ML / Haskellの観点から、いくつかの発言をします。

Basileが言及しているように、アクセス制御の概念はML / Haskellに存在し、頻繁に使用されます。「ファクタリング」は、従来のOOP言語とは少し異なります。OOPでは、クラスの概念はtypemoduleの役割を同時に果たしますが、関数型(および従来の手続き型)言語はこれらを直交的に扱います。

もう1つのポイントは、ML / Haskellは型消去のジェネリックに非常に重く、これを使用してOOPカプセル化とは異なる「情報隠蔽」のフレーバーを提供できることです。コンポーネントがデータ項目の型を型パラメーターとしてのみ知っている場合、そのコンポーネントはその型の値を安全に渡すことができますが、その具体的な型を知らず、知ることができないため、それらで多くのことを行うことはできません(instanceofこれらの言語にはユニバーサルまたはランタイムのキャストはありません)。 このブログエントリは、これらの手法の私のお気に入りの入門例の1つです。

次に:FPの世界では、不透明/カプセル化されたコンポーネントへのインターフェイスとして透明なデータ構造を使用することは非常に一般的です。たとえば、FPでは、ロジックを記述する構文ツリーとしてデータ構造が使用され、それらを「実行」するコードにフィードされるインタープリターパターンが非常に一般的です。適切に言われた状態は、データ構造を消費するインタープリターが実行されるときに一時的に存在します。また、インタプリタの実装は、同じデータ型に関してクライアントと通信している限り変更できます。

最後かつ最長:カプセル化/情報隠蔽はテクニックであり、目的ではありません。それが提供するものについて少し考えてみましょう。カプセル化は、契約とソフトウェアユニットの実装を調整するための手法です。典型的な状況はこれです。システムの実装は、その契約に従って存在すべきでない値または状態を認めます。

このように見れば、FPはカプセル化に加えて、同じ目的で使用できるいくつかの追加ツールを提供することを指摘できます。

  1. 一般的なデフォルトとしての不変性。透明なデータ値をサードパーティのコードに渡すことができます。それらを変更したり、無効な状態にしたりすることはできません。(カールの答えはこの点を示しています。)
  2. 代数データ型を備えた高度な型システム。大量のコードを記述することなく、型の構造を細かく制御できます。これらの機能を慎重に使用することにより、「悪い状態」がまったく不可能なタイプを設計できます。(スローガン:「違法な状態を表現できないようにします。」)カプセル化を使用してクラスの許容可能な状態のセットを間接的に制御する代わりに、コンパイラにそれらが何であるかを伝えて保証します!
  3. すでに説明した通訳パターン。優れた抽象構文ツリータイプを設計するための1つの鍵は、次のとおりです。
    • すべての値が「有効」になるように、抽象構文ツリーのデータ型を試して設計します。
    • それに失敗すると、インタプリタに無効な組み合わせを明示的に検出させ、それらを完全に拒否させます。

このF#「型を使用した設計」シリーズでは、これらのトピックのいくつか、特に#2についてかなり読みやすくしています。(上から「違法状態を表現できないようにする」リンクの由来です。)よく見ると、2番目の部分では、カプセル化を使用してコンストラクターを隠し、クライアントが無効なインスタンスを作成しないようにする方法を示しています。上で言ったように、それツールキットの一部です!


9

可変性がソフトウェアで問題を引き起こす程度を誇張することはできません。私たちの頭に叩き込まれている慣行の多くは、可変性が引き起こす問題を補うものです。可変性を取り除いた場合、これらのプラクティスはそれほど必要ありません。

不変性がある場合、実行時にデータ構造が予期せず下から変化しないことがわかっているので、プログラムに機能を追加するときに、独自の派生データ構造を作成して使用できます。元のデータ構造は、これらの派生データ構造について何も知る必要はありません。

つまり、基本データ構造は非常に安定している傾向があります。新しいデータ構造は、必要に応じてエッジから派生します。重要な機能プログラムを作成するまで、説明するのは非常に困難です。プライバシーをますます気にせず、永続的な汎用パブリックデータ構造を作成することをますます考えています。


私が追加したいことの1つは、不変変数により、プログラマーは分散した散在したデータ構造に固執するということです。すべてのデータは、輸送用ではなく、発見と移動を容易にするために、論理グループを作成するように構成されています。これは、十分な関数型プログラミングを行った後のロジックの進行です。
Xephon

8

私の意見では、Clojureがハッシュとプリミティブを使用する傾向は、機能的遺産の一部ではなく、動的な遺産の一部です。PythonとRuby(オブジェクト指向、命令型、動的、両方とも高階関数のサポートはかなり優れていますが)で同様の傾向が見られましたが、たとえばHaskell(静的に型付けされていますが、純粋に機能的です) 、不変性を回避するために必要な特別な構造を備えています)。

したがって、質問する必要があるのは、関数型言語が大きなAPIをどのように処理するかではなく、動的言語がどのように処理するかです。答えは:優れたドキュメントとたくさんの単体テストです。幸いなことに、現代の動的言語は通常、両方を非常によくサポートしています。たとえば、PythonとClojureの両方には、コメントだけでなく、コード自体にドキュメントを埋め込む方法があります。


静的に型付けされた(純粋に)関数型言語について、オブジェクト指向プログラミングのようにデータ型を持つ関数を実行する(単純な)方法はありません。とにかく、ドキュメントは重要です。ポイントは、インターフェイスを定義するために言語サポートが必要ないことです。
Scara95

5
@ Scara95「データ型で関数を実行する」という意味を詳しく説明できますか?
セバスチャンレッド

6

一部の関数型言語は、抽象データ型およびモジュールの実装の詳細をカプセル化または非表示にする機能を提供します

たとえば、OCamlには、名前付きの抽象型と値のコレクション(特にこれらの抽象型で動作する関数)で定義されたモジュールがあります。そのため、ある意味で、OcamlのモジュールはAPIを変更しています。Ocamlには、いくつかのモジュールを別のモジュールに変換し、汎用プログラミングを提供するファンクターもあります。したがって、モジュールは構成的です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.