最近、関数型プログラミングのいくつかの概念に興味を持っています。私はしばらくの間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')
ここで本当に求めているのは、関数型言語でポリモーフィズムを実行する方法、つまり、引数に基づいて異なる動作をする関数を作成する方法です。
通常、関数の最初の引数はOOPの「オブジェクト」に相当しますが、関数型言語では通常関数をデータから分離する必要があるため、 「オブジェクト」は、純粋な(不変の)データ値である可能性があります。
一般的に関数型言語は、ポリモーフィズムを実現するためのさまざまなオプションを提供します。
例として、マルチメソッドを使用した問題の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%正確であるとは限りません。しかし、どちらの場合でも、私はあなたに私の経験を共有します...
1年ほど前、私はあなたと同じような船に乗っていました。私はC++とC#を実行しましたが、すべてのデザインは常にOOPに非常に負荷がかかりました。 FP言語について聞いて、オンラインで情報を読んだり、F#ブックをめくったりしましたが、FP言語をOOPに置き換える方法をまだ理解できていません。または私が見たほとんどの例は単純すぎるので、一般的に有用です。
私にとってpythonを学ぶことを決心したとき、「突破口」が訪れました。私はpythonをダウンロードしてから project euler homepage に行き、次々と問題を起こし始めました。 Pythonは必ずしもFP言語であるとは限らず、その言語でクラスを作成できることは確かですが、C++/Java/C#と比較すると、はるかに多くのFPがあります。 _コンストラクトなので、それをいじり始めたとき、どうしても必要な場合を除いて、クラスを定義しないことを意識的に決定しました。
Pythonについて私が興味深いと思ったのは、関数を取得してそれらを "ステッチ"してより複雑な関数を作成するのがいかに簡単で自然かであり、結局のところ、問題は単一の関数を呼び出すことで解決されました。
あなたは、コーディングするとき、あなたは単一の責任の原則に従うべきであり、それは絶対に正しいと指摘しました。しかし、関数が単一のタスクを担当するからといって、それが絶対最小限のことしかできないということではありません。 FPでは、抽象化レベルがまだあります。したがって、上位レベルの関数は「1つ」の処理を実行できますが、「1つ」の処理の詳細を実装するために、下位レベルの関数に委任できます。
ただし、FPの重要な点は、副作用がないことです。アプリケーションを入力と出力のセットが定義された単純なデータ変換として扱う限り、必要なことを実行するFPコードを書くことができます。明らかに、すべてのアプリケーションがこの型にうまく収まるわけではありませんが、それを始めると、いくつのアプリケーションが収まるのか驚かれることでしょう。そして、Python、F#、またはScalaはFP構文を提供するので、ここが素晴らしいと思いますが、状態を覚えて「副作用を導入」する必要がある場合は、いつでも落ちることができます。 trueに戻り、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:.. ....)」と書いたはずなのに、クラスを作成しただけです。
Haskellでは、最も近いのは「クラス」です。ただし、このクラスはJavaおよびC++のクラスと同じではありません)が、この場合に必要な機能を果たします。
あなたの場合、これはあなたのコードがどのように見えるかです。
class Animal a where say :: String-> sound
次に、これらのメソッドを適応させる個々のデータ型を設定できます。
instance Animal Dog where say s = "bark" ++ s
編集:-あなたが犬のために言うことを専門にすることができる前に、あなたは犬が動物であることをシステムに伝える必要があります。
data Dog = \-something here-\(動物の派生)
編集:-Wilqの場合。
これで、say foo関数でsayを使用する場合、fooはアニマルでしか機能しないことをhaskellに伝える必要があります。
foo ::(動物a)=> a->文字列->文字列 foo a str = str a str
犬でfooを呼び出すと鳴き、猫で呼び出すと鳴きます。
main = do let d = dog(\-cstr parameters-\) c = cat in show $ foo d "Hello World"
これで、関数のその他の定義を言うことはできなくなりました。動物以外の何かでsayを呼び出すと、コンパイルエラーが発生します。
関数型言語は2つの構造を使用して多態性を実現します。
これらを使用してポリモーフィックコードを作成する方法は、OOPが継承と仮想メソッドを使用する方法とは完全に異なります。これらの両方は、お気に入りのOOP言語(C# )、ほとんどの関数型言語(Haskellのような)は11までキックします。関数が非ジェネリックになることはまれであり、ほとんどの関数はパラメーターとして関数を持っています。
このように説明するのは難しく、この新しい方法を学ぶには多くの時間を必要とします。しかし、これを行うには、OOPを完全に忘れる必要があります。これは、関数型の世界ではOOPが機能しないためです。
それは本当にあなたが達成したいことに依存します。
選択的な基準に基づいて動作を整理する方法が必要な場合は、たとえば、関数オブジェクトを含む辞書(ハッシュテーブル)。 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)dogまたはcatの「インスタンス」はなく、(b)自分でオブジェクトの「タイプ」を追跡する必要があることに注意してください。 。
例:_pets = [['martin','dog','grrrh'], ['martha', 'cat', 'zzzz']]
_。次に、[animals[pet[1]]['say'](pet[2]) for pet in pets]
のようなリスト内包を行うことができます
OO言語は、低レベル言語の代わりに使用して、マシンと直接やり取りすることができます。 C++もちろんですが、C#でもアダプターなどがあります。機械部品を制御し、メモリを細かく制御するコードを記述することは、できる限り低レベルに保つのが最善です。しかし、この質問が、基幹業務、Webアプリケーション、IOT、Webサービス、および大部分の大量使用アプリケーションなどの現在のオブジェクト指向ソフトウェアに関連している場合は、...
読者は、サービス指向アーキテクチャー(SOA)を使用してみてください。つまり、DDD、N層、N層、六角形など。 70年代と80年代にこの10年間で非常に多く説明されていたように、大規模なビジネスアプリケーションが「従来の」OO(Active-RecordまたはRich-Models))を効率的に使用するのを見たことはありません。(注1を参照)
障害はOPにあるのではありませんが、質問にはいくつかの問題があります。
あなたが提供する例は、単にポリモーフィズムを示すためのものであり、本番用のコードではありません。時々、まさにそのような例が文字通り取られます。
FPとSOAでは、データはビジネスロジックから分離されます。つまり、データとロジックは一緒に実行されません。ロジックはサービスに移行し、データ(ドメインモデル)はポリモーフィックな動作をしません(注2を参照)。
サービスと関数はポリモーフィックにすることができます。 FPでは、関数を値としてではなくパラメーターとして他の関数に渡すことがよくあります。 OO CallableやFuncのようなタイプの言語でも同じことができますが、蔓延することはありません(注3を参照)。FP and SOA 、モデルはポリモーフィックではなく、サービス/関数のみです(注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);
}
}
OOのクラスから関数型プログラミングにどのように移行しますか?他の人が言ったように、可能ですが、実際にはそうではありません。上記のポイントは、 JavaおよびC#を実行するときにクラスを(伝統的な世界の意味で)使用します。サービス指向アーキテクチャ(DDD、階層化、階層化、六角形など)でコードを記述したら、データ(ドメインモデル)を論理関数(サービス)から分離するため、機能性に一歩近づきます。
あなたは少しそれをさらに取り、あなたのSOAサービスを2つのタイプに分割するかもしれません。
オプションクラスタイプ1:エントリポイントの共通インターフェイス実装サービス。これらは、「純粋な」または「純粋でない」他の機能を呼び出すことができる「純粋でない」エントリポイントです。これは、RESTful APIからのエントリポイントになる可能性があります。
オプションクラスタイプ2:純粋なビジネスロジックサービス。これらは、「純粋な」機能を持つ静的クラスです。 FPでは、「純粋」は副作用がないことを意味します。 StateやPersistenceを明示的に設定することはありません。 (注5を参照)
そのため、サービス指向アーキテクチャで使用されているオブジェクト指向言語のクラスを考えると、OOコードにメリットがあるだけでなく、関数型プログラミングが非常に理解しやすく見えるようになります。
注1:オリジナルの「リッチ」または「アクティブレコード」オブジェクト指向設計はまだ存在しています。 10年以上前に人々が「正しく実行」していた頃には、そのようなレガシーコードがたくさんあります。前回、その種のコード(正しく行われた)がC++のビデオゲームコードベースのものであり、メモリを正確に制御し、スペースが非常に限られていたことを確認しました。言うまでもなくFPとサービス指向アーキテクチャは野獣であり、ハードウェアを考慮すべきではありません。しかし、それらは絶えず変更、維持、可変データサイズ、およびその他の側面を優先する機能を配置します。ビデオゲームやマシンAIでは、信号やデータを非常に正確に制御します。
注2:ドメインモデルにはポリモーフィックな動作がなく、外部依存関係もありません。それらは「分離」されています。それは彼らが100%貧血でなければならないという意味ではありません。それらが適用される場合、それらはそれらの構築および変更可能なプロパティ変更に関連する多くのロジックを持つことができます。 DDD「値オブジェクト」と、Eric EvansおよびMark Seemannによるエンティティを参照してください。
注3:LinqとLambdaは非常に一般的です。しかし、ユーザーが新しい関数を作成するとき、パラメーターとしてFuncまたはCallableを使用することはほとんどありませんが、FPでは、そのパターンに従った関数のないアプリを見るのはおかしいでしょう。
注4:ポリモーフィズムと継承を混同しないでください。 CatModelは、AnimalBaseを継承して、動物が通常持つプロパティを決定する場合があります。しかし、私が示すように、このようなモデルはCode Smellです。このパターンが表示された場合は、それを分解してデータに変換することを検討してください。
注5:純粋な関数は関数をパラメーターとして受け入れることができます(実際に受け入れます)。入ってくる関数は不純かもしれませんが、純粋かもしれません。テスト目的では、常に純粋です。しかし、本番環境では、純粋なものとして扱われますが、副作用が含まれる場合があります。それは純粋な関数が純粋であるという事実を変えません。パラメータ関数は不純かもしれませんが。混乱しないでください! :D