web-dev-qa-db-ja.com

「高次関数」機能は、抽象化とカプセル化を許可/維持できますか?

以下は、関数パラダイムを使用して記述されたrepeat関数です。repeat(square, 2)(5)として呼び出されると、square関数_2_が_5_に適用されます。square(square(5))

_def repeat(f, n):
    def identity(x):
        return x
    def apply_n_times(n):
        def recursive_apply(x):
            return apply_n_times(n - 1)(f(x))

        if n < 0:
            raise ValueError("Cannot apply a function %d times" % (n))
        Elif n == 0:
            return identity 
        else:
            return recursive_apply
    return apply_n_times(n)

def square(x):
    return mul(x, x)
_

抽象化に関しては、repeat(square, 2)が実際の結果を提供する前に、apply_n_times(n - 1)(f(x))の形式で実装の詳細を複数回返すことがわかります。

カプセル化に関して、式f = repeat(square, 2)の場合、関数オブジェクトのメンバーを変更できます。例:_f.__name__='garbage'_

_higher order function_の概念により、抽象化とカプセル化をサポートできますか?実装の詳細を返し、変更のためのアクセスを提供するためです。

大規模なソフトウェアでのこのような既存の実装は、ユーザーが使用する前に実装のアイデアを持っている必要があるため、使用するのが非常に面倒です。

6
overexchange

抽象化については、repeat(square、2)が実際の結果を提供する前に、apply_n_times(n-1)(f(x))の形式で実装の詳細を複数回返すことがわかります。

repeat(square, 2)によって返される関数は、実装の詳細ではありません。それがrepeatを呼び出す重要なポイントです。実装の詳細は、呼び出し元が知る必要のないものであり(ほとんどの場合、どちらも知ることが許可されていません)、呼び出し元のコードを壊すことなく変更できます。呼び出し元は、repeatによって返される関数を必要とし、必要とします。

repeatは、fを適用した結果を提供する目的であるかのように見ているように見えます。そのため、実装の詳細としてapply_n_timesを返すことがわかります。 fn回だけで合成した結果だけが必要な場合は、repeatを定義して3つの引数を取ることができます。しかし、カリー化された関数が便利になるのは、作成された中間の関数を利用できるという事実です!例えば。

numbers = range(1, 10)
numbersSquaredTwice = map(repeat(square, 2), numbers)

言い換えれば、それらの目的は最終結果を計算することではなく、最終結果を計算できる関数を計算することです。 repeatによって返される関数は、それ自体が持つ便利なものです。

カプセル化に関して、式f = repeat(square、2)の場合、関数オブジェクトのメンバーを変更できます。例:f .name= 'garbage'

Pythonのすべてには、検査および変更できるメタデータがあり、関数オブジェクトも例外ではありません。Pythonは、プログラム内のデータの一部です。そうすることで、ほとんどすべての抽象化を解除できます。つまり、Pythonこれは、カプセル化をあまりサポートしていないと言えます。言語は、関数がその名前を知ることを意味しません。関数に名前を求めることは、名前に整数を求めることと同じくらい意味があるためです。関数は名前を必要としません。それを知る必要はありません。

ただし、repeatを呼び出すたびにnew関数オブジェクトが生成されることに注意してください。そのため、いじくり回しても、行った変更は行われません。 repeatの他の呼び出しの戻り値に影響します。

14
Doval

抽象化の利点は、呼び出し側が実装の詳細を知る必要がないことです。私が正しく理解していれば、これはpython構成に疑問を投げかけています。これは、呼び出し元が実装の詳細を見つけることができるためですこれは同じことではありません。

メソッドがどのように機能するかを知る必要がないのは便利です。 1つの詳細レベルを同時に考えずに、同時に詳細を検討できるため、ソフトウェア開発者の仕事を支援します。

見つけられないことは、まったく異なるものです。ライブラリプログラマーに、何も壊さずに後で最適化するためのより大きな自由を与えることは有用です。しかし実際には、APIで単に「返されたヘルパーオブジェクトを通じて可視の中間型はコントラクトの一部ではなく、それらを使用することは未定義の動作です」と宣言するだけで十分です。このようにして、ライブラリの作成者は同じ自由度を保持し、明示的な契約を意図的に破る呼び出し元のみがそれに苦しみますが、主な利点(詳細を無視できること)は変わりません。

7
Kilian Foth

呼び出し元は、その戻り値を知る必要があるため、repeat(f, n)の実装の詳細を知る必要はあまりありません。この場合、戻り値は関数を返すため、ほとんどの場合よりも少し複雑です。 Haskellのような静的言語では、これは次のような関数シグネチャで簡単に文書化されます。

repeat :: (a -> a) -> Int -> (a -> a)

これは、aを取り、aを返す関数を返すことを示していますが、その関数の実装の詳細を知る必要はなく、シグネチャとセマンティクスだけを知っている必要があります。

pythonのような動的言語の主な弱点は、戻り値の型を含む、それらの型が関数シグネチャに文書化されていないことです(主な長所は、プログラマーがそれらを文書化することを強制されないことです)。これにより、comment/docstringに戻りの型を文書化するか、呼び出しコードの関数本体またはコンテキストの手がかりを読み取ってそれらを決定することができます。これは常にall関数、高次のものだけではありません。関数は一般的に小さく、問題がないほど単純であり、使用した複雑なサードパーティのコードは一般的に優れたdocstringを持っているため、おそらく気付かなかったでしょう。

Pythonには、言語に依存するのではなく、慣習によって情報が隠されるという伝統があります。プライベートな実装の詳細であるかどうかを判断できない場合は、それも文書化する必要があります。一般的に、クロージャーの本体は、返されるかどうかに関係なく、プライベートな実装の詳細であると見なします。

6
Karl Bielefeldt