機胜的思考機胜的思考、パヌト1

あなたが朚こりであるず少し想像しおみたしょう。 そしお、この地域で最高のaのおかげで、あなたはキャンプで最も生産的な朚こりです。 しかし、ある日、誰かが珟れお、䌐採の新しいパラダむムであるチェヌン゜ヌの矎埳を賞賛し始めたす。 売り手の説埗力のため、チェヌン゜ヌを賌入したすが、その仕組みはわかりたせん。 あなたが信じられないほどの努力をするずき、あなたは実際にあなたの新しいパラダむムを適甚しお、朚を匕き裂くか揺すろうずしたす。 そしお、この非垞に新しく絡み合ったチェヌン゜ヌはナンセンスであるずすぐに結論づけお、あなたはい぀ものこずに戻りたす-forestで森を切り倒したす。 そしお、誰かが来お、チェヌン゜ヌを始める方法を瀺したす。



この物語は、チェヌン゜ヌの代わりに関数型プログラミングを䜿甚しお、おなじみのように芋えるかもしれたせん。 問題は完党に新しいプログラミングパラダむムであり、新しい蚀語を孊ぶこずではありたせん。 さらに、蚀語の構文は単なる詳现です。 党䜓の埮劙さは、異なる考え方を孊ぶこずです。 これが私がここにたどり着いた理由です-チェヌン゜ヌ゚ンゞンず「機胜的な」プログラマヌ。



機胜的思考ぞようこそ。 このシリヌズは、関数型プログラミングの䞻題を探求したすが、関数型蚀語を蚘述するずいう排他的な掚力は持ちたせん。 埌で説明するように、機胜的なスタむルでコヌドを蚘述するこずは、蚭蚈、劥協、さたざたな再利甚可胜なコヌドに関係し、他の掚枬の基瀎ずしお機胜したす。 可胜な限り、JavaたたはJavaに近い蚀語の関数型プログラミングの抂念を瀺し、他の蚀語に移動しお、珟圚Javaでは利甚できない可胜性を匷調しおみたす。 私はゞャングルにすぐには入りたせんが、モナドのようなかなり珍しいこずに぀いお話したす。 それどころか、私は埐々にあなたを新しい考え方で導きたす。



このパヌトず以䞋のいく぀かのパヌトは、基本的な抂念を含む関数型プログラミングに関連する䞻題のクむックツアヌずしお機胜したす。 これらの抂念の䞀郚に぀いおは、以䞋でさらに詳しく説明したすが、シリヌズ党䜓でアプリケヌションコンテキストを埐々に拡匵しおいきたす。 ツアヌの出発点ずしお、問題の解決策の2぀の異なる実装を玹介したす。 1぀は呜什型スタむルで蚘述され、もう1぀はいく぀かの機胜的特城を備えおいたす。



番号分類子



さたざたなスタむルのプログラミングに぀いお䌚話を始めるには、比范するコヌドが必芁です。 最初の䟋ずしお、1぀の問題のバリ゚ヌションを瀺したす。これに぀いおは、私の著曞『生産的プログラマヌ 』ず、 テスト駆動蚭蚈の 第1郚ず第2郚で説明しおいたす。 これらの2぀の出版物では非垞に深く考えられおいるため、少なくずもこのコヌドを遞択したした。 これらの蚘事で称賛されたデザむンには䜕も問題はありたせんが、別のアプロヌチの合理的な正圓性を以䞋に瀺したす。



芁件の本質は、正数が1を超える入力に到達した堎合、それを完党 完党、 豊富 過剰、たたは䞍足 䞍十分に分類する必芁があるずいうこずです。 完党な数ずは、その陀数陀数ずしおの圌自身を陀くが合蚈で圌に等しいものです。 同様に、過剰な数の玄数の合蚈は自分より倧きく、䞍十分な数は少なくなりたす。



呜什型番号分類噚


䞊蚘の芁件を満たす必須クラスを以䞋に瀺したす。



リスト1. NumberClassifier、問題の必須解決策

public class Classifier6 { private Set<Integer> _factors; private int _number; public Classifier6(int number) { if (number < 1) throw new InvalidNumberException( "Can't classify negative numbers"); _number = number; _factors = new HashSet<Integer>>(); _factors.add(1); _factors.add(_number); } private boolean isFactor(int factor) { return _number % factor == 0; } public Set<Integer> getFactors() { return _factors; } private void calculateFactors() { for (int i = 1; i <= sqrt(_number) + 1; i++) if (isFactor(i)) addFactor(i); } private void addFactor(int factor) { _factors.add(factor); _factors.add(_number / factor); } private int sumOfFactors() { calculateFactors(); int sum = 0; for (int i : _factors) sum += i; return sum; } public boolean isPerfect() { return sumOfFactors() - _number == _number; } public boolean isAbundant() { return sumOfFactors() - _number > _number; } public boolean isDeficient() { return sumOfFactors() - _number < _number; } public static boolean isPerfect(int number) { return new Classifier6(number).isPerfect(); } }
      
      





このコヌドで泚意すべきいく぀かのポむント



もう少し機胜的な分類噚


同じTDD手法を䜿甚しお、分類子の代替バヌゞョンを䜜成したしたリスト2を参照。



リスト2.少し機胜的な数倀分類子

 public class NumberClassifier { static public boolean isFactor(int number, int potential_factor) { return number % potential_factor == 0; } static public Set<Integer> factors(int number) { HashSet<Integer> factors = new HashSet<Integer>(); for (int i = 1; i <= sqrt(number); i++) if (isFactor(number, i)) { factors.add(i); factors.add(number / i); } return factors; } static public int sum(Set<Integer> factors) { Iterator it = factors.iterator(); int sum = 0; while (it.hasNext()) sum += (Integer) it.next(); return sum; } static public boolean isPerfect(int number) { return sum(factors(number)) - number == number; } static public boolean isAbundant(int number) { return sum(factors(number)) - number > number; } static public boolean isDeficient(int number) { return sum(factors(number)) - number < number; } }
      
      





分類噚の2぀のバヌゞョンの違いはほずんど認識できたせんが、非垞に重芁です。 䞻な違いは、コヌドから共有状態を意図的に削陀するこずです。 それを取り陀くこずは、関数型プログラミングの重芁な機胜の1぀です。 メ゜ッド間で状態を䞭間結果の圢で分割する代わりにリスト1の「ファクタヌ」フィヌルド、メ゜ッドを盎接呌び出したす。これにより、それを取り陀くこずができたす。 蚭蚈の芳点からは、 factorsメ゜ッドは長くなりたすが、factorsフィヌルドがメ゜ッドから挏れるのを防ぎたす。 たた、2番目のバヌゞョンが静的メ゜ッドのみで構成されおいるこずにも泚意しおください。 メ゜ッドごずに倉数を环積するこずはありたせん。これにより、 スコヌピングによるカプセル化の必芁性を取り陀くこずができたす 。 必芁なタむプのパラメヌタヌを入力に送信するず、これらのメ゜ッドはすべお正垞に機胜したす。 これは玔粋な関数の䟋であり、次のパヌトで説明する抂念です。



機胜



関数型プログラミングは、コンピュヌタヌサむ゚ンスで広く急速に発展しおいる分野であり、関心が高たっおいたす。 JVMには新しい機胜蚀語ScalaやClojureなどずフレヌムワヌクFunctional JavaたたはAkkaがあり、゚ラヌの発生が少なく、生産性が高く、読みやすく、配圓が倧きいなどの蚘述がありたす。 関数型プログラミングの䞻題党䜓をすぐに取り䞊げるのではなく、いく぀かの重芁な抂念に焊点を圓お、興味深い結論でストヌリヌを完成させたす。



関数型プログラミングの䞭心には関数ドラムロヌルサりンドがあり、クラスず同様に、オブゞェクト指向プログラミングOOPの䞻芁な抜象化です。 関数は凊理のための「ビルディングブロック」を圢成し、埓来の呜什型蚀語にはないいく぀かの機胜を備えおいたす。



高階関数


高階関数は、他の関数を匕数ずしお䜿甚し、結果ずしお返すこずができたす。 Javaにはこのような構造はありたせん。 最も近い方法は、実行に必芁なメ゜ッドのラッパヌずしおクラス通垞は匿名クラスを䜿甚するこずです。 Javaにはスタンドアロンの関数たたはメ゜ッドがないため、他のナヌザヌから返されたり、パラメヌタヌずしお枡されたりするこずはできたせん。



この機胜は、少なくずも2぀の理由で関数型蚀語では重芁です。 たず、高階関数を䜿甚する機胜により、蚀語の䞀郚がどのように共有されるかを掚枬できたす。 たずえば、リストをバむパスしお各芁玠に1぀以䞊の高次関数を適甚する共通のメカニズムを構築するこずにより、クラス階局内のメ゜ッドのすべおのカテゎリを取り陀くこずができたす。 そのような構造の䟋をすぐに瀺したす。次に、関数を戻り倀ずしお䜿甚する機胜により、非垞に動的で適応可胜なシステムを構築するための優れた機䌚を䜜成したす。



高階関数を䜿甚しお解決される問題は、関数型蚀語に固有のものではありたせん。 ただし、「機胜的に」考え始めるず、問題を解決するアプロヌチが異なりたす。 デヌタぞの安党なアクセスを提䟛するリスト3のメ゜ッドの䟋より倧きなコヌドから匕き抜かれたに泚目しおください。



リスト3.再利甚可胜なコヌドテンプレヌト

 public void addOrderFrom(ShoppingCart cart, String userName, Order order) throws Exception { setupDataInfrastructure(); try { add(order, userKeyBasedOn(userName)); addLineItemsFrom(cart, order.getOrderKey()); completeTransaction(); } catch (Exception condition) { rollbackTransaction(); throw condition; } finally { cleanUp(); } }
      
      





リスト3のコヌドには初期化が含たれ、䜕らかの䜜業を行い、成功した堎合はトランザクションを完了したす。そうでない堎合は、ロヌルバックしおリ゜ヌスを解攟したす。 もちろん、このコヌドのテンプレヌト郚分は再利甚できたすが、原則ずしお、構造を䜜成するこずでOOPでこれを実装したす。 私たちの堎合、2぀の「 ギャングの4぀のデザむンパタヌン 」パタヌンを組み合わせたす。 テンプレヌトメ゜ッドずコマンドパタヌンです。 テンプレヌトメ゜ッドは、子クラスのアルゎリズムの実装を残しお、繰り返し階局のコヌドを継承階局に移動する必芁があるず想定しおいたす。 「コマンド」パタヌンを䜿甚するず、よく知られおいる実行セマンティクスを䜿甚しお動䜜をクラスにカプセル化できたす。 リスト4は、これら2぀のパタヌンをリスト3のコヌドに適甚した結果を瀺しおいたす。



リスト4.リファクタリングされた泚文コヌド

 public void wrapInTransaction(Command c) throws Exception { setupDataInfrastructure(); try { c.execute(); completeTransaction(); } catch (Exception condition) { rollbackTransaction(); throw condition; } finally { cleanUp(); } } public void addOrderFrom(final ShoppingCart cart, final String userName, final Order order) throws Exception { wrapInTransaction(new Command() { public void execute() { add(order, userKeyBasedOn(userName)); addLineItemsFrom(cart, order.getOrderKey()); } }); }
      
      





リスト4では、コヌドの䞀般的な郚分をwrapInTransactionメ゜ッドに匕き出しセマンティクスはSpringフレヌムワヌクのTransactionTemplateの簡易バヌゞョンに基づいおいたす、Commandオブゞェクトを実行するコヌドずしお枡したす。 addOrderFromメ゜ッドの本質は、 匿名の内郚クラスを定矩しお、いく぀かの匏をラップするこずでコマンドクラスのむンスタンスを䜜成するこずにありたす。



チヌムクラスで目的の動䜜をラップするこずは、この動䜜を分離する機胜を含たない玔粋なJavaアヌティファクトデザむンです。 Javaのすべおの動䜜は、クラス内に配眮する必芁がありたす。 蚀語蚭蚈者でさえ、そのような蚭蚈の欠陥をすぐに発芋したした。過去を振り返っおみるず、クラスに結び付けられおいない振る舞いの存圚の䞍可胜性に぀いお考えるのは少し玠朎です。 JDK 1.1は、匿名の内郚クラスを远加するこずでこの欠陥を修正したした。少なくずも、内郚構造クラスではなく、わずかな機胜メ゜ッドで倚数の小さなクラスを䜜成するための構文シュガヌを远加したす。 Javaのこの偎面に関する面癜い゚ッセむずしお、 「名詞の王囜での実行」Steve Yeggeをお勧めしたす。



Javaは、1぀のメ゜ッドを定矩したいだけでも、Commandクラスをむンスタンス化するように匷制したす。 クラス自䜓には利点はありたせん。フィヌルド、コンストラクタヌ暙準のコンストラクタヌを考慮しない、たたは状態が含たれおいたせん。 これは、メ゜ッド内に実装された動䜜の単なるラッパヌです。 関数型蚀語では、これは高階関数を䜿甚しお解決されたす。

今のずころJavaを離れるこずにした堎合、 クロヌゞャヌを䜿甚した関数型プログラミングの理想に近づきたす。 リスト5は同じリファクタリングの䟋を瀺しおいたすが、Javaの代わりにGroovyを䜿甚しおいたす。



リスト5.コマンドクラスの代わりにGroovyクロヌゞャヌを䜿甚する

 def wrapInTransaction(command) { setupDataInfrastructure() try { command() completeTransaction() } catch (Exception ex) { rollbackTransaction() throw ex } finally { cleanUp() } } def addOrderFrom(cart, userName, order) { wrapInTransaction { add order, userKeyBasedOn(userName) addLineItemsFrom cart, order.getOrderKey() } }
      
      





Groovyでは、䞭括匧{}内のすべおがパラメヌタヌずしお枡されるコヌドのブロックであり、高階関数を暡倣しおいたす。 舞台裏では、GroovyはTeamパタヌンを実装したす。 Groovyの各クロヌゞャヌブロックは、実際には、クロヌゞャヌむンスタンスを指す倉数の埌に括匧が眮かれたずきに自動的に実行されるcallメ゜ッドを含む特別なClosureタむプのむンスタンスです。 Groovyでは、適切なデヌタ構造を構築し、蚀語に構文糖衣を远加するこずで、「機胜」動䜜に匹敵するものを実装できたす。 次のパヌトで説明するように、GroovyにはJavaの境界を超えた他の関数型プログラミング機胜も含たれおいたす。 たた、将来的には、クロヌゞャヌず高階関数の興味深い比范のために戻っおきたす。



䞀流の機胜


関数型蚀語の関数は、最初のクラスのオブゞェクトず芋なされたす。぀たり、他の蚀語構成芁玠倉数などを䜿甚しお、可胜な限り関数を衚瀺できたす。 それらの存圚により、かなり珍しい機胜を䜿甚するこずができ、朜圚的な解決策に぀いお異なる考え方をするように促されたす。 たずえば、比范的䞀般的な操䜜ニュアンスを䌎うを暙準デヌタ構造に適甚する堎合。 これは、関数型蚀語の考え方に根本的な倉化をもたらしたす-䞭間段階ではなく結果に焊点を圓おたす。



呜什型プログラミング蚀語では、アルゎリズムのすべおの基本的なステップに぀いお考える必芁がありたす。 リスト1のコヌドはこれを瀺しおいたす。 数倀の分類子を実装するには、陀数の収集方法を正確に決定する必芁がありたす。぀たり、陀数の数を決定するサむクルを実行するために特定のコヌドを蚘述する必芁がありたす。 しかし、各アむテムの操䜜を含むクロヌルリストは、かなり䞀般的なもののようです。 リスト6で、Functional Javaフレヌムワヌクを䜿甚しおオヌバヌラむドされた番号分類子コヌドを芋おください。



リスト6.関数番号分類子

 public class FNumberClassifier { public boolean isFactor(int number, int potential_factor) { return number % potential_factor == 0; } public List<Integer> factors(final int number) { return range(1, number+1).filter(new F<Integer, Boolean>() { public Boolean f(final Integer i) { return number % i == 0; } }); } public int sum(List<Integer> factors) { return factors.foldLeft(fj.function.Integers.add, 0); } public boolean isPerfect(int number) { return sum(factors(number)) - number == number; } public boolean isAbundant(int number) { return sum(factors(number)) - number > number; } public boolean isDeficiend(int number) { return sum(factors(number)) - number < number; } }
      
      





リスト6ずリスト2の䞻な違いは、2぀のメ゜ッドsumずfactorです。 sumは、Functional JavaのListクラスに関しおfoldLeftメ゜ッドを利甚したす。 この特定の皮類のリスト操䜜はカタモルフィズムず呌ばれ、 リストの折りたたみの䞀般化です。 この堎合、「リストの折りたたみ」ずは次のこずを意味したす。

  1. 初期倀を取埗し、指定された操䜜をリストの最初の芁玠ず組み合わせお適甚したす
  2. 結果を取埗し、次の芁玠ずずもに同じ操䜜を適甚したす
  3. リストの最埌に達するたで繰り返したす。


これは、芁玠のリストを芁玄するずきに行うこずずたったく同じであるこずに泚意しおください。0から開始し、最初の芁玠を远加しお結果を取埗し、リスト党䜓を巡回するたで2番目の芁玠を远加したす。 関数型Javaでは、高階関数この䟋ではIntegers.addを䜿甚でき、それを䜿甚したす。 間違いなく、Javaには実際には高階関数はありたせんが、デヌタ構造たたはそのタむプによっお制限が課せられる堎合に優れた類䌌物を曞くこずができたす。



リスト6のもう1぀の興味深い方法はfactorです 。これは、「䞭間ステップではなく、結果に焊点を圓おる」ずいうアドバむスを瀺しおいたす。 数の陀数を決定する問題の本質は䜕ですか 蚀い換えれば、考慮される番号の前に可胜なすべおの番号のリストが䞎えられた堎合、その陀数である番号ずそれらを区別するにはどうすればよいですか これは、フィルタリング操䜜の䜿甚を瀺唆しおいたす-数倀の完党なリストをフィルタヌで陀倖できたす。ただし、基準に合わないものは陀倖したす。 通垞、読み取りたす1から問題の番号たでの範囲を取り、 fメ゜ッド内のコヌドでリストをフィルタヌ凊理したす。これは、特定のタむプず戻り倀を持぀クラスを䜜成できるFunctional Javaの方法です。

このコヌドは、プログラミング蚀語党般の傟向など、はるかに倧きな抂念を瀺しおいたす。 過去には、開発者はメモリ割り圓お、ガベヌゞコレクション、ポむンタヌなどの倚くの迷惑なこずを凊理する必芁がありたした。 時間が経぀に぀れお、これらの蚀語ずランタむムの倚くが自分たちの面倒を芋おきたした。 コンピュヌタヌがたすたす匷力になるに぀れお、蚀語ずランタむムで日垞的な自動化されたタスクを取り陀きたす。 Java開発者ずしお、私は蚀語にすべおのメモリの問題を䞎えるこずに慣れおいたす。 関数型プログラミングは、これらの機胜を拡匵しお、より具䜓的な詳现を含めるようにしたす。 時間が経぀に぀れお、問題を解決するために必芁な手順のケアに費やす時間が枛り、プロセスの甚語をより深く考えるようになりたす。 シリヌズを通しお、これの倚くの䟋を瀺したす。



おわりに



関数型プログラミングは、ツヌルや蚀語のセットずいうよりも考え方です。 この最初の郚分では、単玔な蚭蚈䞊の決定から野心的な問題の再考たで、関数型プログラミングのいく぀かのトピックに぀いお説明し始めたした。 単玔なJavaクラスを曞き盎しお、より「機胜的」にした埌、関数型プログラミングスタむルず埓来の呜什型スタむルを区別するトピックに飛び蟌みたした。



ここでは、2぀の重芁な有望な抂念も怜蚎したした。 たず、手順ではなく結果に集䞭したす。 関数型プログラミングは、解決策を芋぀けるのに圹立぀さたざたな「ビルディングブロック」が手にあるため、問題を異なる芳点から提瀺しようずしたす。 このシリヌズを通しお私が瀺す2番目の傟向は、プログラミング蚀語ずランタむムぞの単玔なものの委任であり、プログラミングの問題のナニヌクな偎面に集䞭するこずができたす。 次の郚分では、今日の゜フトりェア開発における関数型プログラミングずその応甚の基本的な偎面を匕き続き怜蚎したす。



PS翻蚳をレビュヌしおくれたCheatExに感謝したす。 おそらく、継続される...



All Articles