どの機能機能がもたらすメリットについて、OOPを少し混乱させる価値がありますか?


13

HaskellとF#で関数型プログラミングを学んだ後、OOPパラダイムはクラス、インターフェース、オブジェクトで後戻りしているように見えます。同僚が理解できるFPのどの側面を仕事に持ち込めますか?使用できるようにチームを再訓練することについて、上司と話す価値のあるFPスタイルはありますか?

FPの可能な側面:

  • 不変性
  • 部分適用とカレー
  • ファーストクラス関数(関数ポインター/機能オブジェクト/戦略パターン)
  • 遅延評価(およびモナド)
  • 純粋な機能(副作用なし)
  • 式(対ステートメント-コードの各行は、副作用の代わりに、または副作用を引き起こすことに加えて、値を生成します)
  • 再帰
  • パターンマッチング

それは、プログラミング言語がサポートしている限り、その言語がサポートする制限まで何でもできる、すべての人にとって自由なものですか?または、より良いガイドラインがありますか?


6
私も同じような経験をしました。約2か月の苦労の後、「オブジェクトにマップするもの」と「関数にマップするもの」の非常に良いバランスを見つけ始めました。両方をサポートする言語でいくつかの深刻なハッキングを行うのに役立ちます。それの終わりには、私のFPとOOPのスキルの両方が大幅に改善されている
ダニエルGratzer

3
FWIW、Linqは機能的怠zyであり、静的メソッドを使用して状態の永続性を回避することにより、C#で機能プログラミングをシミュレートできます。
ロバートハーベイ

1
この時点で、sicpを読む必要があります。無料でよく書かれています。2つのパラダイムをうまく比較できます。
サイモンベルゴ

4
FPとOOPは、ある意味では直交であり、ある意味ではデュアルです。OOPはデータの抽象化に関するものであり、FPは副作用(の不在)に関するものです。副作用があるかどうかは、データを抽象化する方法とは直交します。ラムダ計算は、たとえば機能的でオブジェクト指向です。はい、FPは通常、オブジェクトではなく抽象データ型を使用しますが、FPを少なくすることなく、代わりにオブジェクトを使用することもできます。大藤、また深い関係があります:関数は、(1つの方法だけで対象と同型である彼らは、「偽造」Javaで、とに実装されているかのJava8、...
イェルクWミッターク

3
あなたの質問の最も強い側面は、読みやすさに関係していると思います。「オブジェクト指向ショップで仕事をするのに適切な関数型プログラミングスタイルはどれくらいですか?」または、どの機能機能がもたらすメリットについては、OOPを少し混乱させる価値がありますか。
グレンペターソン

回答:


13

関数型プログラミングは、オブジェクト指向プログラミングとは異なるパラダイムです(考え方が異なり、プログラムについての考え方が異なります)。ここで、問題とその解決策について考えるための複数の方法(オブジェクト指向)があることに気付き始めています。他にもあります(手続き型および汎用プログラミングが思い浮かびます)。この新しい知識にどのように反応するか、これらの新しいツールとアプローチを受け入れてスキルセットに統合するかどうかによって、成長してより完全でスキルのある開発者になるかどうかが決まります。

私たちは全員、取り扱いの訓練を受けており、ある程度の複雑さに慣れています。私はこの人の呼びたいhrairの上限(ウォーターシップダウンからの、どのように高いあなたが数えることができます)。心を広げ、より多くの選択肢を検討し、問題にアプローチして解決するためのツールを増やすことができるのは素晴らしいことです。しかし、それは変化であり、あなたをあなたの快適ゾーンから引き離します。

発生する可能性のある問題の1つは、「すべてがオブジェクトである」群衆に従うコンテンツが少なくなることです。ソフトウェア開発への機能的アプローチが特定の問題に対してうまく機能する理由を理解していない(または理解したくない)人と作業するとき、忍耐を養う必要があるかもしれません。特定の問題に対して汎用プログラミングのアプローチがうまく機能するように。

幸運を!


3
HaskellやClojureなどの関数型言語を使用する場合、従来のOOPの概念をよりよく理解できることを付け加えます。個人的には、ポリモーフィズムが実際に重要な概念(JavaのインターフェースまたはHaskellの型クラス)であることに気付きましたが、継承(定義概念と考えていたもの)は奇妙な抽象化のようなものです。
ウィルベル

6

関数型プログラミングは、毎日のコード作成で非常に実用的で現実的な生産性をもたらします。一部の機能は簡潔さを優先します。これは、書くコードが少なく、失敗が少なく、メンテナンスが少ないため素晴らしいです。

数学者なので、ファンシーな機能的なものは非常に魅力的ですが、通常はアプリケーションの設計に役立ちます。これらの構造は、変数によってこれらの不変式を表すことなく、プログラムの多くの不変式をエンコードできます。

私のお気に入りの組み合わせはかなり些細に見えるかもしれませんが、生産性に非常に大きな影響があると思います。この組み合わせは、部分アプリケーションとカリー化ファーストクラス関数でありラベルを付け直すことは決してforループを記述しません。代わりに、ループの本体を反復関数またはマッピング関数に渡します。私は最近C ++の仕事に就職しましたが、面白いことに気付きました。for ループを書く習慣を完全に失いました

再帰パターンマッチングの組み合わせは、そのVisitorデザインパターンの必要性を消滅させます。ブール式のエバリュエーターをプログラムするために必要なコードを比較するだけです。関数型プログラミング言語では、これは約15行のコードである必要があります。OOPでは、Visitorデザインパターンを使用するのが適切です。広範なエッセイ。利点は明らかであり、不便な点はありません。


2
私は完全に同意しますが、業界全体で同意する傾向がある人々からプッシュバックがありました:彼らは訪問者パターンを知っており、それを何度も見て使用しているので、その中のコードは彼らが理解し慣れているものです他のアプローチはとんでもないほど簡単で簡単ですが、外国人であるため、彼らにとってより困難です。これは、10年以上繰り返した後に100行以上を記憶しているために、100行以上のコードが10行よりも簡単に理解できるという15行以上のOOPがすべてのプログラマーの頭に押し込まれたという業界の不幸な事実です
ジミー・ホッファ

1
-1-より簡潔なコードは、「少ない」コードを書いていることを意味しません。より少ない文字を使用して同じコードを書いています。どちらかといえば、コードが(多くの場合)読みにくいため、より多くのエラーが発生します。
テラスティン

8
@Telastyn:Terseは、読み取り不能と同じではありません。また、肥大化したボイラープレートの巨大な塊には、読み取り不可能な独自の方法があります。
マイケルショー

1
@Telastynここで本当の核心部分に触れたと思います。はい、ひどく読みにくく、肥大化もひどく、読みにくいことがありますが、重要なのは可変長と不可解なコードではありません。キーを使用すると、操作の数の上に言及して、私は操作の数は、保守性に相関、私は(明確に書かれたコードで)あまり物事を考えていないことを同意されない利益の可読性と保守性を。明らかに、単一の文字機能や変数名で物事の同じ数を行うことは助けにはなりません、良いFPが必要かなりまだはっきりと書かれた以下の操作を
ジミー・ホッファ

2
@ user949300:完全に異なる例が必要な場合、このJava 8の例はどうですか?:list.forEach(System.out::println);FPの観点から、println関数は2つの引数、ターゲットPrintStreamと値を取りますObjectが、CollectionforEachメソッドは1つの引数のみを持つ関数を期待していますこれはすべての要素に適用できます。そのため、最初の引数はSystem.out、1つの引数を持つ新しい関数を生成する際に見つかったインスタンスにバインドされます。それはより簡単ですBiConsumer<…> c=PrintStream::println; PrintStream a1=System.out; list.forEach(a2 -> c.accept(a1, a2));
ホルガー

5

スーパーマンがクラークケントのふりをして、日常生活の特典を楽しむために、職場で使用する知識のどの部分を制限する必要があるかもしれません。しかし、より多くを知ることは決してあなたを傷つけません。とはいえ、関数型プログラミングのいくつかの側面はオブジェクト指向のショップに適しています。他の側面は上司に相談する価値があるので、ショップの平均的な知識レベルを上げ、結果としてより良いコードを書くことができます。

FPとOOPは相互に排他的ではありません。Scalaを見てください。FPが不純であるために最悪だと考える人もいれば、同じ理由で最高だと考える人もいます。

以下に、OOPでうまく機能するいくつかの側面を示します。

  • 純粋な機能(副作用なし)-私が知っているすべてのプログラミング言語がこれをサポートしています。それらはあなたのコードをずっと推論しやすくし、実用的であればいつでも使うべきです。FPと呼ぶ必要はありません。それを良いコーディング慣行と呼んでください。

  • 不変性:文字列は間違いなく最も一般的に使用されるJavaオブジェクトであり、不変です。私はカバー不変のJavaオブジェクト不変のJavaコレクション私のブログに。その一部はあなたに適用されるかもしれません。

  • ファーストクラス関数(関数ポインター/機能オブジェクト/戦略パターン)-Javaには、リスナーインターフェイスを実装するほとんどのAPIクラス(および数百あります)を備えたバージョン1.1以来、これの変形バージョンがあります。Runnableは、おそらく最も一般的に使用される機能オブジェクトです。ファーストクラス関数は、ネイティブにサポートされていない言語でコードを作成するのにより多くの作業を行いますが、コードの他の側面を単純化する場合は、余分な労力が必要になる場合があります。

  • 再帰はツリーの処理に役立ちます。OOPショップでは、これがおそらく再帰の主な適切な使用方法です。OOPの楽しみのために再帰を使用することは、ほとんどのOOP言語がデフォルトでスタックスペースを持たないこと以外の理由でこれを良いアイデアとしないなら、おそらく嫌われるべきです。

  • 式(対ステートメント-コードの各行は、副作用の代わりに、または副作用を引き起こすことに加えて)値を生成します-C、C ++、およびJavaの唯一の評価演算子はTernary演算子です。ブログで適切な使用法について説明します。高度に再利用可能で評価可能ないくつかの単純な関数を作成する場合があります。

  • 遅延評価(およびモナド)-ほとんどがOOPの遅延初期化に制限されています。それをサポートするための言語機能がなければ、便利なAPIを見つけることができますが、独自のAPIを書くのは困難です。代わりにストリームの使用を最大化します-例については、WriterおよびReaderインターフェイスを参照してください。

  • 部分適用とカリー化-ファーストクラスの機能なしでは実用的ではありません。

  • パターンマッチング-通常、OOPでは推奨されません。

要約すると、作業は、プログラミング言語がサポートしている限り、プログラミング言語がサポートできる範囲で何でもできる自由な仕事ではないと思います。同僚による読みやすさは、雇用のために作成されたコードのリトマス試験であると思います。それがあなたを最も苦しめるところでは、同僚の視野を広げるために職場での教育を始めることを検討します。


FPを習得してから、表現に似たもの、つまり多くのことを行う1つのステートメントを持つ関数をもたらす流なインターフェイスを持つように物事を設計する習慣がありました。これは実際に最も近いものですが、ボイドメソッドがなくなったときに純粋に自然に流れるアプローチです。C#で静的拡張メソッドを使用すると、これが非常に役立ちます。あなたの表現のポイントは、私が反対する唯一のポイントであるように、他のすべては、私自身の経験FPを学習し、.NETの日の仕事を作業と上のスポットである
ジミー・ホッファ

C#で本当に気になっているのは、2つの単純な理由で、1メソッドインターフェイスの代わりにデリゲートを使用できないことです。1。コンビネータ(C#の地獄と同じくらいい)。2.プロジェクトスコープで使用できる型エイリアスはないため、デリゲートの署名はすぐに管理不能になります。だから、これらの2つの愚かな理由のために、私はもうC#を楽しむことができません。
トライデントダガオ

@bonomo Java 8には、C#で役立つ可能性のある汎用java.util.function.BiConsumerがあります。java.util.functionにpublic interface BiConsumer<T, U> { public void accept(T t, U u); }は他の便利な機能インターフェースがあります。
グレンペターソン

@bonomoわかりました、これはHaskellの痛みです。「FPを学ぶことでOOPが改善された」と言う人は、RubyやHaskellのような純粋で宣言的なものではないことを学んだことを意味します。Haskellは、OOPが無駄に低いレベルであることを明確にしています。あなたが直面している最大の痛みは、HM型システムを使用していない場合、制約ベースの型推論が決定できないため、cosntraintベースの型推論が完全に行われないことです:blogs.msdn.com/b/ericlippert/archive / 2012/03/09 / ...
ジミー・ホッファ

1
Most OOP languages don't have the stack space for it 本当に?必要なのは、バランスの取れた二分木で数十億のノードを管理するための30レベルの再帰です。私のスタックスペースはこれよりも多くのレベルに適していると確信しています。
ロバートハーベイ

3

関数型プログラミングとオブジェクト指向プログラミングに加えて、宣言型プログラミング(SQL、XQuery)もあります。各スタイルを学ぶことは、新しい洞察を得るのに役立ち、仕事に適したツールを選ぶことを学びます。

しかし、ええ、言語でコードを書くのは非常にイライラする可能性があります。また、何か他のものを使用している場合、特定の問題領域に対してより生産的になる可能性があることを知っています。ただし、Javaなどの言語を使用している場合でも、回り道ではありますが、FPの概念をJavaコードに適用することは可能です。たとえば、Guavaフレームワークはこれの一部を実行します。


2

プログラマーとして、学習をやめるべきではないと思います。とはいえ、FPの学習があなたのOOPスキルを汚しているのは非常に興味深いことです。OOPを学ぶことは、自転車の乗り方を学ぶことと考える傾向があります。あなたはそれを行う方法を決して忘れません。

FPの内と外を学んだとき、私は自分がより数学的に考え、自分がソフトウェアを書く手段のより良い視点を獲得したことに気付きました。それが私の個人的な経験です。

経験を積むにつれて、コアプログラミングの概念を失うことははるかに難しくなります。したがって、OOPの概念が完全に固まるまで、FPで簡単に実行することをお勧めします。FPは明確なパラダイムシフトです。幸運を!


4
OOPの学習は、クロールの学習に似ています。しかし、着実に足を踏み入れると、酔っているときにのみonlyうようになります。もちろん、それを行う方法を忘れることはできませんが、通常はしたくないでしょう。そして、あなたが走ることができるとわかっているとき、クローラーと一緒に歩くのはつらい経験になるでしょう。
SKロジック

@ SK-logic、私はあなたのメタファーが好きです
トライデントD'Gao

@ SK-Logic:命令型プログラミングの学習とはどのようなものですか?胃の上で自分を引きずりますか?
ロバートハーベイ

@RobertHarveyはさびたスプーンとパンチカードのデッキで地下に穴を掘ろうとしています。
ジミーホッファ

0

すでに多くの良い答えがありますので、私はあなたの質問のサブセットに対処します。つまり、OOPと機能の特徴は相互に排他的ではないので、私はあなたの質問の前提に無理をします。

C ++ 11を使用する場合、これらの種類の機能プログラミング機能が言語/標準ライブラリに組み込まれ、OOPと(かなり)相乗効果を発揮します。もちろん、上司や同僚がTMPをどれだけ受信できるかはわかりませんが、ポイントは、これらの機能の多くをC ++などの非機能/ OOP言語で何らかの形で取得できることです。

コンパイル時再帰でテンプレートを使用するには、最初の3つのポイントに依存します。

  • 不変性
  • 再帰
  • パターンマッチング

そのテンプレート値は不変(コンパイル時定数)であり、反復は再帰を使用して行われ、分岐は(多かれ少なかれ)オーバーロード解決の形式でパターンマッチングを使用して行われます。

他の点に関しては、を使用するstd::bindと、std::function部分的な関数アプリケーションが使用できます。また、関数ポインターは言語に組み込まれています。呼び出し可能オブジェクトは、機能オブジェクト(および部分的な機能アプリケーション)です。呼び出し可能なオブジェクトとは、それらを定義するオブジェクトを意味することに注意してくださいoperator ()

遅延評価と純粋な関数は少し難しくなります。純粋な関数の場合、値のみをキャプチャするラムダ関数を使用できますが、これは理想的ではありません。

最後に、部分関数アプリケーションでコンパイル時再帰を使用する例を示します。これはやや不自然な例ですが、上記のポイントのほとんどを示しています。指定されたタプルの値を指定された関数に再帰的にバインドし、(呼び出し可能な)関数オブジェクトを生成します

#include <iostream>
#include <functional>

//holds a compile-time index sequence
template<std::size_t ... >
struct index_seq
{};

//builds the index_seq<...> struct with the indices (boils down to compile-time indexing)
template<std::size_t N, std::size_t ... Seq>
struct gen_indices
  : gen_indices<N-1, N-1, Seq ... >
{};

template<std::size_t ... Seq>
struct gen_indices<0, Seq ... >
{
    typedef index_seq<Seq ... > type;
};


template <typename RType>
struct bind_to_fcn
{
    template <class Fcn, class ... Args>
    std::function<RType()> fcn_bind(Fcn fcn, std::tuple<Args...> params)
    {
        return bindFunc(typename gen_indices<sizeof...(Args)>::type(), fcn, params);
    }

    template<std::size_t ... Seq, class Fcn, class ... Args>
    std::function<RType()> bindFunc(index_seq<Seq...>, Fcn fcn, std::tuple<Args...> params)
    {
        return std::bind(fcn, std::get<Seq>(params) ...);
    }
};

//some arbitrary testing function to use
double foo(int x, float y, double z)
{
    return x + y + z;
}

int main(void)
{
    //some tuple of parameters to use in the function call
    std::tuple<int, float, double> t = std::make_tuple(1, 2.04, 0.1);                                                                                                                                                                                                      
    typedef double(*SumFcn)(int,float,double);

    bind_to_fcn<double> binder;
    auto other_fcn_obj = binder.fcn_bind<SumFcn>(foo, t);
    std::cout << other_fcn_obj() << std::endl;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.