クラスを使用したOOPと比較した関数型プログラミング


32

私は最近、関数型プログラミングの概念のいくつかに興味を持っています。しばらくの間、OOPを使用しました。OOPでかなり複雑なアプリを作成する方法を見ることができます。各オブジェクトは、そのオブジェクトが行うことを行う方法を知っています。または、親クラスでも同様です。だから私は単にPerson().speak()人に話させるように言うことができます。

しかし、関数型プログラミングで同様のことを行うにはどうすればよいですか?関数がどのようにファーストクラスのアイテムであるかがわかります。しかし、その機能は特定の1つのことだけを行います。私は単にsay()周りに浮かぶメソッドを持ち、それを同等のPerson()引数で呼び出すので、何か言っていることを知っていますか?

だから私は簡単なことを見ることができます、機能プログラミングでOOPとオブジェクトの比較をどのように行うのですか?それで、コードベースをモジュール化して整理できますか?

参考までに、OOPでの私の主な経験は、Python、PHP、およびいくつかのC#です。私が見ている機能的な機能を持つ言語は、ScalaとHaskellです。私はScalaに傾いていますが。

基本的な例(Python):

Animal(object):
    def say(self, what):
        print(what)

Dog(Animal):
    def say(self, what):
        super().say('dog barks: {0}'.format(what))

Cat(Animal):
    def say(self, what):
        super().say('cat meows: {0}'.format(what))

dog = Dog()
cat = Cat()
dog.say('ruff')
cat.say('purr')

ScalaはOOP + FPとして設計されているため、選択する必要はありません
Karthik T

1
はい、私は承知していますが、知的理由で知りたいです。関数型言語のオブジェクトに相当するものは何も見つかりません。scalaに関しては、関数型over oopをいつ、どこで、どのように使用すべきかを知りたいのですが、私見は別の質問です。
スキフト

2
「特に強調しすぎると、IMOは状態を維持しないという概念です。」:これは間違った概念です。FPが状態を使用しないのは本当ではなく、むしろFPは状態を異なる方法で処理します(たとえば、HaskellのモナドまたはCleanの一意の型)。
ジョルジオ


回答:


21

ここで実際に質問しているのは、関数言語で多態性を行う方法、つまり引数に基づいて異なる動作をする関数を作成する方法です。

関数の最初の引数は通常、OOPの「オブジェクト」と同等ですが、関数型言語では通常、関数をデータから分離する必要があるため、「オブジェクト」は純粋な(不変の)データ値である可能性があります。

一般に関数型言語は、多態性を実現するためのさまざまなオプションを提供します。

  • 提供された引数の検査に基づいて異なる関数を呼び出すマルチメソッドのようなもの。これは、最初の引数のタイプ(ほとんどのOOP言語の動作と事実上等しい)で実行できますが、引数の他の属性でも実行できます。
  • ファーストクラスの関数をメンバーとして含むプロトタイプ/オブジェクトのようなデータ構造。したがって、犬と猫のデータ構造内に「say」関数を埋め込むことができます。事実上、データのコード部分を作成しました。
  • パターンマッチング -パターンマッチングロジックが関数定義に組み込まれ、パラメーターごとに異なる動作を保証します。Haskellで一般的。
  • 分岐/条件 -OOPのif / else句に相当します。高度に拡張可能ではないかもしれませんが、可能な値のセットが限られている場合(たとえば、関数に数値または文字列またはnullが渡された場合)、多くの場合に適切です。

例として、マルチメソッドを使用した問題のClojure実装を以下に示します。

;; define a multimethod, that dispatched on the ":type" keyword
(defmulti say :type)  

;; define specific methods for each possible value of :type. You can add more later
(defmethod say :cat [animal what] (println (str "Car purrs: " what)))
(defmethod say :dog [animal what] (println (str "Dog barks: " what)))
(defmethod say :default [animal what] (println (str "Unknown noise: " what)))

(say {:type :dog} "ruff")
=> Dog barks: ruff

(say {:type :ape} "ook")
=> Unknown noise: ook

この動作では、明示的なクラスを定義する必要がないことに注意してください。通常のマップは正常に機能します。ディスパッチ関数(この場合、:type)は、引数の任意の関数です。


100%明確ではありませんが、行き先を確認するには十分です。これは、特定のファイル内の「動物」コードとして見ることができます。また、分岐/条件に関する部分も良好です。私はそれをif / elseの代替とは考えていませんでした。
スキフト

11

私は関数型言語の専門家ではないので、これは直接的な答えではなく、必ずしも100%正確ではありません。しかし、どちらの場合でも、私の経験をあなたと共有します...

約1年前、私はあなたと同じような船に乗っていました。私はC ++とC#を作成しましたが、私の設計はすべてOOPで常に非常に重いものでした。私はFP言語について聞いた、オンラインで情報を読んだ、F#の本をめくったが、FP言語がOOPに取って代わる方法を理解できなかった、または私が見たほとんどの例があまりにも単純だったので、一般的に有用である。

私にとって「ブレークスルー」は、私がpythonを学ぶことに決めたときに来ました。私はpythonをダウンロードしてから、オイラーのホームページを作成し、次々と問題を起こし始めました。Pythonは必ずしもFP言語ではなく、確実にクラスを作成できますが、C ++ / Java / C#と比較すると、はるかに多くのFPコンストラクトがあります。したがって、Pythonを使い始めたとき、絶対に必要でない限り、クラスを定義します。

Pythonで面白いと思ったのは、関数を取得し、それらを「ステッチ」してより複雑な関数を作成するのがどれほど簡単で自然だったのかということでした。

あなたは、コーディングの際には単一の責任原則に従うべきであり、それは絶対に正しいと指摘しました。しかし、関数が単一のタスクを担当するからといって、絶対に最低限のことしかできないというわけではありません。FPでは、抽象化レベルがまだあります。したがって、上位レベルの関数はまだ「1」のことを実行できますが、下位レベルの関数に委任して、「1」のことをどのように達成するかの詳細を実装できます。

ただし、FPの重要な点は、副作用がないことです。定義された入力セットと出力セットを持つ単純なデータ変換としてアプリケーションを扱う限り、必要なことを達成するFPコードを書くことができます。明らかに、すべてのアプリケーションがこの型にうまく適合するとは限りませんが、いったんそれを始めると、いくつのアプリケーションが適合するのか驚くでしょう。Python、F#、またはScalaは、FPコンストラクトを提供するので優れていると思いますが、状態を覚えて「副作用を導入する」必要がある場合は、いつでも真のOOPテクニックを試すことができます。

それ以来、私は多くのpythonコードを内部作業用のユーティリティおよびその他のヘルパースクリプトとして記述し、それらのいくつかはかなり遠くまでスケールアウトしましたが、基本的なSOLIDの原則を思い出すことで、そのコードの大部分は依然として非常に保守可能で柔軟です。OOPの場合と同じように、インターフェイスはクラスであり、機能のリファクタリングや追加を行うとクラスを移動します。FPでは、関数でまったく同じことを行います。

先週、Javaでコーディングを開始して以来、ほぼ毎日、OOPの場合、関数をオーバーライドするメソッドでクラスを宣言することでインターフェイスを実装する必要があることを思い出しました。場合によっては、Pythonで同じことを実現できます。単純なラムダ式、たとえば、ディレクトリをスキャンするために作成した20〜30行のコードは、Pythonでは1〜2行で、クラスはありません。

FP自体は高レベル言語です。Python(申し訳ありませんが、私の唯一のFPエクスペリエンス)では、リスト内包表記をラムダなどの別のリスト内包表記に組み込むことができ、コード全体は3〜4行になります。C ++では、まったく同じことを達成できましたが、C ++は低レベルであるため、3〜4行よりもはるかに多くのコードを記述する必要があり、行数が増えると、SRPトレーニングが開始され、開始されますコードをより小さな部分に分割する方法(つまり、より多くの機能)を考える。しかし、保守性と実装の詳細を隠すために、これらのすべての関数を同じクラスに入れ、プライベートにします。そして、あなたはそれを持っています...私はクラスを作成しましたが、Pythonでは「return(.... lambda x:.. ....)」と書きました


はい、それは質問に直接答えませんが、それでも素晴らしい反応です。Pythonで小さなスクリプトまたはパッケージを作成するときは、常にクラスを使用しないでください。多くの場合、パッケージ形式で持っているだけで完全に適合します。特に状態が必要ない場合。リストの内包表記も非常に有用であることに同意します。FPを読んで以来、私はそれらがどれほど強力であるかに気付きました。その後、OOPと比較してFPについてもっと知りたいと思いました。
スキフト

素晴らしい答え。機能的なプールの横に立っているすべての人に話すが、つま先を水に浸すかどうかわからない
Robben_Ford_Fan_boy

そして、Ruby ...その設計哲学の1つは、引数としてコードブロックをとるメソッド(通常はオプション)に集中しています。そして、この方法で構文を考えてコーディングするのは簡単です。C#でこのように考えて構成することは困難です。C#の令状は機能的に冗長であり、見当識障害を引き起こします。私は、Rubyが機能的に簡単に考えるのを助けて、私のC#思考ボックスに可能性を見出したことを気に入っています。最終的な分析では、機能的およびオブジェクト指向が補完的であると考えています。Rubyは確かにそう考えていると思います。
レーダーボブ

8

Haskellでは、最も近いのは「クラス」です。このクラスは、JavaおよびC ++のクラスと同じではありませんが、この場合に必要なものに対して機能します。

あなたの場合、これはあなたのコードがどのように見えるかです。

クラス動物aどこ 
say :: String-> sound 

その後、これらの方法に適応する個々のデータ型を使用できます。

インスタンスAnimal Dogここで
say s = "bark" ++ s 

編集:-犬に特化する前に、犬が動物であることをシステムに伝える必要があります。

data Dog = \-something here-\(動物由来)

編集:-ウィルク。
関数fooで関数sayを使用したい場合、fooがAnimalでしか動作できないことをhaskellに伝える必要があります。

foo ::(動物a)=> a->文字列->文字列
foo a str = str a say 

fooをdogで呼び出すと鳴き、catで呼び出すと鳴きます。

main = do 
let d = dog(\-cstr parameters-\)
    c =猫  
ショー$ foo d "Hello World"

これで、関数の定義の他の定義を持つことはできません。sayが動物以外のもので呼び出された場合、コンパイルエラーが発生します。


病気を完全に理解するには、haskellでもう少し詳しく説明する必要がありますが、その要点を理解できたと思います。しかし、これがより複雑なコードベースとどのように並ぶかについてはまだ興味があります。
スキフト

nitpick動物は大文字にする必要があります
ダニエル

1
say関数は、文字列のみを受け取る場合にDogで呼び出すことをどのように知るのですか?また、一部の組み込みクラスのみを「派生」させているのではないでしょうか?
WilQu

6

関数型言語は、2つの構造を使用して多態性を実現します。

  • 一次関数
  • ジェネリック

それらでポリモーフィックコードを作成することは、OOPが継承と仮想メソッドを使用する方法とはまったく異なります。どちらもお気に入りのOOP言語(C#など)で使用できますが、ほとんどの関数型言語(Haskellなど)は11個まで使用できます。関数が非ジェネリックになることはまれであり、ほとんどの関数にはパラメーターとして関数があります。

このように説明するのは難しく、この新しい方法を学ぶには多くの時間が必要です。しかし、これを行うには、OOPが機能的な世界でどのように機能するかではないため、OOPを完全に忘れる必要があります。


2
OOPはすべてポリモーフィズムです。OOPがデータに関連付けられた関数を持つことだと思うなら、OOPについて何も知らないでしょう。
陶酔

4
ポリモーフィズムはOOPの1つの側面に過ぎず、OPが本当に求めているものではないと思います。
Doc Brown

2
多態性はOOPの重要な側面です。それをサポートするために他のすべてがあります。継承/仮想メソッドを使用しないOOPは、手続き型プログラミングとほぼまったく同じです。
陶酔

1
@ErikReppen「インターフェイスを適用する」が頻繁に必要でない場合、OOPを実行していません。また、Haskellにはモジュールもあります。
陶酔

1
常にインターフェイスが必要なわけではありません。ただし、必要なときに非常に便利です。そして、IMOはOOPのもう1つの重要な部分です。Haskellのモジュールに関しては、コード編成に関する限り、これはおそらく関数型言語のOOPに最も近いと思います。少なくともこれまで読んだことから。私は彼らがまだ非常に異なっていることを知っています。
スキフト

0

本当に何を達成したいかに依存します。

選択基準に基づいて動作を整理する方法が必要な場合は、関数オブジェクトで辞書(ハッシュテーブル)などを使用できます。Pythonでは、次のような行になります:

def bark(what):
    print "barks: {0}".format(what) 

def meow(what):
    print "meows: {0}".format(what)

def climb(how):
    print "climbs: {0}".format(how)

if __name__ == "__main__":
    animals = {'dog': {'say': bark},
               'cat': {'say': meow,
                       'climb': climb}}
    animals['dog']['say']("ruff")
    animals['cat']['say']("purr")
    animals['cat']['climb']("well")

ただし、(a)またはの「インスタンス」はなく、(b)オブジェクトの「タイプ」を自分で追跡する必要があります。

例えば好き:pets = [['martin','dog','grrrh'], ['martha', 'cat', 'zzzz']]。次に、次のようなリストの理解を行うことができます[animals[pet[1]]['say'](pet[2]) for pet in pets]


0

低レベル言語の代わりにオブジェクト指向言語を使用して、マシンと直接やり取りすることができます。C ++確かに、ただしC#でもアダプターなどがあります。機械部品を制御し、メモリを細かく制御するコードを記述することは、可能な限り低レベルに近づけることが最善です。しかし、この質問が、基幹業務、Webアプリケーション、IOT、Webサービス、および大部分の大量使用アプリケーションのような現在のオブジェクト指向ソフトウェアに関連している場合、...

該当する場合は回答

読者は、サービス指向アーキテクチャ(SOA)を使用してみてください。つまり、DDD、N層、N層、六角形などです。過去10年間で70年代および80年代に説明されたように、大規模ビジネスアプリケーションが「伝統的な」OO(アクティブレコードまたはリッチモデル)を効率的に使用しているのを見たことはありません。(注1を参照)

障害はOPにありませんが、質問にはいくつかの問題があります。

  1. 提供する例は、単にポリモーフィズムを示すためのものであり、量産コードではありません。時々、まさにそのような例が文字通りに取られます。

  2. FPおよびSOAでは、データはビジネスロジックから分離されています。つまり、データとロジックは連動しません。ロジックはサービスに入り、データ(ドメインモデル)にはポリモーフィックな動作がありません(注2を参照)。

  3. サービスと機能はポリモーフィックにすることができます。FPでは、関数を値としてではなく他の関数にパラメーターとして頻繁に渡します。CallableやFuncなどの型を使用して、オブジェクト指向言語で同じことを実行できますが、横行することはありません(注3を参照)。FPおよびSOAでは、モデルはポリモーフィックではなく、サービス/機能のみです。(注4を参照)

  4. その例では、ハードコーディングの悪いケースがあります。私は赤い文字列「犬の樹皮」について話しているだけではありません。CatModelとDogModel自体についても話します。羊を追加する場合はどうなりますか?あなたのコードに入り、新しいコードを作成する必要がありますか?どうして?本番コードでは、むしろそのプロパティを備えたAnimalModelのみを表示します。最悪の場合、AmphibianModelとFowlModelのプロパティと処理が大きく異なる場合。

これは、現在の「OO」言語で見られると予想されるものです。

public class Animal
{
    public int AnimalID { get; set; }
    public int LegCount { get; set; }
    public string Name { get; set; }
    public string WhatISay { get; set; }
}

public class AnimalService : IManageAnimals
{
    private IPersistAnimals _animalRepo;
    public AnimalService(IPersistAnimals animalRepo) { _animalRepo = animalRepo; }

    public List<Animal> GetAnimals() => _animalRepo.GetAnimals();

    public string WhatDoISay(Animal animal)
    {
        if (!string.IsNullOrWhiteSpace(animal.WhatISay))
            return animal.WhatISay;

        return _animalRepo.GetAnimalNoise(animal.AnimalID);
    }
}

基本的な流れ

オブジェクト指向のクラスから関数型プログラミングにどのように移行しますか?他の人が言ったように。できますが、実際はそうではありません。上記のポイントは、JavaとC#を実行するときに、クラスを(従来の世界の感覚で)使用するべきではないことを示すことです。サービス指向アーキテクチャ(DDD、階層化、階層化、六角形など)でコードを記述すると、論理関数(サービス)からデータ(ドメインモデル)を分離するため、機能に一歩近づきます。

FPに一歩近づくOO言語

あなたはそれをもう少し進めてSOAサービスを2つのタイプに分けることさえできます。

オプションのクラスタイプ1:エントリポイントの共通インターフェイス実装サービス。これらは、「純粋な」または「不純な」他の機能を呼び出すことができる「不純な」エントリポイントになります。これは、RESTful APIからのエントリポイントかもしれません。

オプションのクラスタイプ2:純粋なビジネスロジックサービス。これらは「純粋な」機能を持つ静的クラスです。FPでは、「純粋」は副作用がないことを意味します。StateまたはPersistenceをどこにも明示的に設定しません。(注5を参照)

したがって、サービス指向アーキテクチャで使用されているオブジェクト指向言語のクラスを考えると、オブジェクト指向コードにメリットがあるだけでなく、関数型プログラミングの理解が非常に簡単になります。

ノート

注1:オリジナルの「リッチ」または「アクティブレコード」オブジェクト指向デザインはまだ存在します。10年以上前に人々が「正しく実行」していた頃のようなレガシーコードがたくさんあります。前回、その種のコード(正しく行われた)が、メモリを正確に制御し、スペースが非常に限られていたビデオゲームのC ++のCodebaseからのものであることがわかりました。FPおよびサービス指向アーキテクチャが獣であり、ハードウェアを考慮するべきではないことは言うまでもありません。しかし、それらは絶えず変化し、維持され、可変のデータサイズを持ち、優先事項として他の側面を持つ能力を置きます。ビデオゲームとマシンAIでは、信号とデータを非常に正確に制御します。

注2:ドメインモデルには多態的な動作はなく、外部依存関係もありません。それらは「分離」されています。それは、彼らが100%貧血である必要があるという意味ではありません。それらは、構築と可変プロパティの変更に関連する多くのロジックを持っている場合があります。Eric EvansとMark SeemannによるDDD「Value Objects」とエンティティを参照してください。

注3:LinqとLambdaは非常に一般的です。しかし、ユーザーが新しい関数を作成するとき、FuncやCallableをパラメーターとして使用することはめったにありませんが、FPでは、そのパターンに従う関数のないアプリを見るのは奇妙です。

注4:ポリモーフィズムと継承を混同しないでください。CatModelは、AnimalBaseを継承して、動物が通常持つプロパティを決定する場合があります。しかし、私が示すように、このようなモデルはコード臭です。このパターンが表示された場合は、それを分解してデータに変換することを検討してください。

注5:純粋な関数は、関数をパラメーターとして受け入れることができます(実際に受け入れます)。入ってくる関数は不純かもしれませんが、純粋かもしれません。テスト目的では、常に純粋です。しかし、本番環境では、純粋として扱われますが、副作用が含まれる場合があります。純粋な関数が純粋であるという事実は変わりません。パラメーター関数は不純かもしれませんが。混乱しない!:D


-2

このようなことをすることができます.. php

    function say($whostosay)
    {
        if($whostosay == 'cat')
        {
             return 'purr';
        }elseif($whostosay == 'dog'){
             return 'bark';
        }else{
             //do something with errors....
        }
     }

     function speak($whostosay)
     {
          return $whostosay .'\'s '.say($whostosay);
     }
     echo speak('cat');
     >>>cat's purr
     echo speak('dog');
     >>>dogs's bark

1
反対票を投じたことはありません。しかし、このアプローチは機能的でもオブジェクト指向でもないためだと思います。
マノジR

1
ただし、伝達される概念は、関数型プログラミングで使用されるパターンマッチングに近い$whostosayものです。つまり、実行されるものを決定するオブジェクトタイプになります。上記を変更して、別のパラメーターを追加で受け入れる$whattosayようにすることで、それをサポートする型(など'human')が使用できるようにすることができます。
syockit
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.