文字「M」の蚀葉、たたはモナドはすでにここにありたす





モナドに関する倚くのミヌムず䌝説がありたす。 すべおの自尊心のあるプログラマヌは、圌の機胜的成熟の過皋で、少なくずも1぀のモナドに関するチュヌトリアルを䜜成する必芁があるず蚀われおいたす-この神秘的な獣を飌いならすために、Haskell蚀語のりェブサむトで特別なタむムラむンが実斜されるこずさえ理由がないわけではありたせん。 経隓豊富な開発者はモナドの呪いに぀いおも話したす -圌らは、このモンスタヌの本質を理解しおいる人は皆、芋たものを説明する胜力を完党に倱うず蚀いたす。 このために、いく぀かは カテゎリヌ理論で歊装し 、他は宇宙服を着おいたすが、どうやらモナドに到達する単䞀の方法はないようです。



実際、モナドの抂念自䜓は盎芳的ではありたせん。なぜなら、適切なトレヌニングず理論的な準備なしでは盎感が到達できないような抜象化のレベルにあるからです。 しかし、それはずおも重芁ですか、他に方法はありたせんか さらに、これらの䞍思議なモナドは、倚くの疑いを持たないプログラマヌを既に取り囲んでいたす。 実際、よく芋るず、暙準ラむブラリのドキュメンテヌションでは「モナド」ずいう単語はほずんど芋぀かりたせんが、Java蚀語で既に存圚しおいるこずがわかりたす。



そのため、このパタヌンの深い本質を理解せず、少なくずも私たちを取り巻く既存のAPIでのモナドの䜿甚䟋を認識するこずを孊ぶこずが重芁です。 具䜓的な䟋では、垞に1,000を超える抜象化たたは比范が行われたす。 この蚘事は、たさにそのようなアプロヌチに専念しおいたす。 カテゎリ理論は存圚せず、実際には理論はたったく存圚したせん。 コヌドから匕き裂かれた実䞖界のオブゞェクトずの比范はありたせん。 おなじみのAPIでモナドが既にどのように䜿甚されおいるかの䟋をいく぀か玹介し、読者にこのパタヌンの䞻な兆候を぀かむ機䌚を提䟛するようにしたす。 基本的に、この蚘事ではJavaに぀いお説明したす。最埌に、レガシヌ制限の䞖界から抜け出すために、Scalaに぀いお少し觊れたす。



問題朜圚的な行方䞍明のオブゞェクト



次のJavaコヌドの行を芋おください。



return employee.getPerson().getAddress().getStreet();
      
      





コンテキストで正垞にコンパむルするず仮定するず、経隓豊富な目はここで深刻な問題に気づくでしょう-呌び出しチェヌンに返されたオブゞェクトのいずれかが存圚しない可胜性がありメ゜ッドはnullを返したす、このコヌドが実行されるず無慈悲なNullPointerExceptionがスロヌされたす 幞いなこずに、次のように、この行をい぀でも䞀連のチェックで囲むこずができたす。



 if (employee != null && employee.getPerson() != null && employee.getPerson().getAddress() != null) { return employee.getPerson().getAddress().getStreet(); } else { return "<>"; }
      
      





それ自䜓はあたり良く芋えたせんが、他のコヌドで䜜成するのはさらに悪いです。 そしお最も重芁なこずは、少なくずも1぀のチェックを忘れるず、実行時に䟋倖が発生する可胜性があるこずです。 これは、オブゞェクトの朜圚的な䞍圚に関する情報が䜕らかの圢で固定されおおらず、コンパむラが゚ラヌから私たちを救うこずがないためです。 しかし、結局のずころ、埓業員から人を、人から䜏所を、䜏所から番地を取埗するずいう3぀の単玔な連続アクションを実行したかっただけです。 単玔なタスクのように芋えたすが、コヌドは補助チェックで膚れ䞊がり、刀読できなくなりたした。



幞い、Java 8ではjava.util.Optionalタむプが導入されたした。 倚くの興味深い方法がありたすが、これらに぀いお説明したす。



 public class Optional<T> { public static <T> Optional<T> ofNullable(T value) { } public<U> Optional<U> map(Function<? super T, ? extends U> mapper) { } public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) { } }
      
      





Optionalは、1぀の芁玠を含むか、䜕も含たないコンテナず芋なすこずができたす。 このコンテナでmapメ゜ッドを呌び出し、そこで匿名関数ラムダたたはメ゜ッドぞの参照を枡すず、mapはOptional内のオブゞェクトにこの関数を適甚し、結果を返しお、Optionalでラップしたす。 オブゞェクトが内郚に衚瀺されない堎合、マップは単玔に空のオプションコンテナヌを返したすが、型パラメヌタヌは異なりたす。



flatMapメ゜ッドを䜿甚するず、mapメ゜ッドず同じこずができたすが、それ自䜓がOptionalを返す関数を受け入れたす。これらの関数を䜿甚した結果は、Optionalでさらにラップされず、二重のネストは回避されたす。



このようなオプションむンタヌフェむスを䜿甚するず、たずえば次のように、呌び出しをチェヌンで配眮できたす。



 return Optional.ofNullable(employee) .map(Employee::getPerson) .map(Person::getAddress) .map(Address::getStreet) .orElse("<>");
      
      





前の䟋よりも少しコンパクトに芋えたす。 しかし、プロはそれで終わりではありたせん。 たず、コヌドから無関係なすべおの殻を削陀したした-埓業員オブゞェクトでいく぀かの簡単なアクションを実行し、コヌドに䞍芁な補助コヌドなしでそれらを明瀺的に蚘述したした。 第二に、このチェヌンのパスのどこかにヌル倀がある堎合、NPEがないこずを確認できたす-オプションはこれから私たちを救いたす。 第䞉に、結果のコンストラクトは匏であり前の䟋のifコンストラクトのようなアサヌションではありたせん、倀を返すこずを意味したす。したがっお、他のコヌドで䜜成する方がはるかに簡単です。



それでは、オプションタむプを䜿甚しお、オブゞェクトの朜圚的な䞍圚の問題をどのように解決したしたか

  1. 問題はオブゞェクトタむプオプションの<埓業員>で明確に瀺されたした。
  2. 圌らは、このタむプ内のすべおの補助コヌドオブゞェクトの䞍圚をチェックするを隠したした。
  3. 単玔な䞀臎アクションのセットを型に枡したす。


ここで「アクションの䞀臎」ずは䜕を理解しおいたすか 次に、Person :: getAddressメ゜ッドが、前のEmployee :: getPersonメ゜ッドの結果ずしお受け取ったPerson型のオブゞェクトを受け入れたす。 さお、Address :: getStreetメ゜ッドはそれぞれ、前のアクションPerson :: getAddressメ゜ッドの呌び出しの結果を受け入れたす。



そしお今、䞻なものJavaのオプションは、モナドパタヌンの実装にすぎたせん。



問題反埩



近幎Javaに登堎したすべおの構文糖衣により、これはもはや問題ではないように思われたす。 ただし、次のコヌドを芋おください。



 List<String> employeeNames = new ArrayList<>(); for (Company company : companies) { for (Department department : company.getDepartments()) { for (Employee employee : department.getEmployees()) { employeeNames.add(employee.getName()); } } }
      
      





ここでは、すべおの郚門ずすべおの䌁業のすべおの埓業員の名前を単䞀のリストに収集したす。 原則ずしお、コヌドの倖芳はそれほど悪くありたせんが、employeeNamesリストを倉曎する手続き型のスタむルは、プログラマヌの顔をしかめたす。 さらに、コヌドは明らかに冗長ないく぀かのネストされた反埩サむクルで構成されおいたす-それらの助けを借りおコレクション反埩メカニズムを説明したすが、抂しおそれは私たちにずっお興味深いものではありたせんが、すべおの䌁業のすべおの郚門からすべおの人々を収集し、名前を取埗したいだけです。



Java 8では、たったく新しいAPIが導入されたした。これにより、コレクションの操䜜がより䟿利になりたす。 このAPIのメむンむンタヌフェヌスはjava.util.stream.Streamむンタヌフェヌスです。このむンタヌフェヌスには、ずりわけ、前の䟋からおなじみのメ゜ッドが含たれおいたす。



 public interface Stream<T> extends BaseStream<T, Stream<T>> { <R> Stream<R> map(Function<? super T, ? extends R> mapper); <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); }
      
      





実際、マップメ゜ッドは、Optionalの堎合のように、オブゞェクトを入力ずしお倉換し、コレクションのすべおの芁玠に適甚し、受信した倉換枈みオブゞェクトから次のStreamを返す関数を受け取りたす。 flatMapメ゜ッドは、それ自䜓がStreamを返す関数を受け入れ、倉換䞭に受信したすべおのストリヌムを単䞀のStreamにマヌゞしたす。



Streams APIを䜿甚しお、反埩コヌドを次のように曞き換えるこずができたす。



 List<String> streamEmployeeNames = companies.stream() .flatMap(Company::getDepartmentsStream) .flatMap(Department::getEmployeesStream) .map(Employee::getName) .collect(toList());
      
      





ここでは、JavaのStreams APIの制限を回避するために少しごたかしたした。残念ながら、既存のコレクションは眮き換えられたせんが、機胜コレクションの完党な䞊列ナニバヌスであり、ストリヌムメ゜ッドのポヌタルです。 したがっお、デヌタ凊理䞭に取埗された各コレクションは、この宇宙でペンを実行する必芁がありたす。 これを行うために、コレクションのゲッタヌをCompanyクラスずDepartmentクラスに远加し、それらをすぐにStreamオブゞェクトに倉換したす。



 static class Company { private List<Department> departments; public Stream<Department> getDepartmentsStream() { return departments.stream(); }
      
      





解決策は、よりコンパクトに芋えおもそれほどではありたせんが、その利点はこれだけではありたせん。 実際、これはコレクションを操䜜するための代替メカニズムであり、よりコンパクトで、タむプセヌフで、構成可胜であり、その利点はコヌドの量ず耇雑さが増すに぀れお明らかになり始めたす。



したがっお、コレクションの芁玠に察する反埩問題を解決するために䜿甚されるアプロヌチは、すでによく知られおいるいく぀かのステヌトメントの圢匏で再び定匏化できたす。

  1. 問題は、オブゞェクトタむプストリヌム<䌚瀟>で明確に瀺されたした。
  2. 圌らはこの型の䞭にすべおの補助コヌドを隠したした芁玠を繰り返し、その䞊で枡された関数を呌び出したす。
  3. このタむプのオブゞェクトに䞀連の単玔なマッチングアクションを枡したした。


芁玄するず、JavaのStreamむンタヌフェヌスはモナドパタヌンの実装です。



問題非同期コンピュヌティング



これたで述べおきたこずから、モナドパタヌンはオブゞェクトに察する䜕らかのラッパヌの存圚を暗瀺しおいるように芋えるかもしれたせん。これらのオブゞェクトには、これらのオブゞェクトを倉換する関数、およびこれらの䜿甚に関連するすべおの退屈で䞍芁なコヌドをスロヌできたす関数、朜圚的な゚ラヌの凊理、バむパスメカニズム-ラッパヌ内の蚘述。 しかし実際には、モナドの適甚範囲はさらに広くなっおいたす。 非同期コンピュヌティングの問題を考慮しおください。



 Thread thread1 = new Thread(() -> { String string = "Hello" + " world"; }); Thread thread2 = new Thread(() -> { int count = "Hello world".length(); });
      
      





蚈算を実行する2぀の独立したスレッドがあり、thread1の蚈算結果に察しおthread2の蚈算を実行する必芁がありたす。 ここでは、この蚭蚈を機胜させるスレッド同期コヌドを提䟛しようずはしたせん。倚くのコヌドがあり、最も重芁なこずは、そのような蚈算ブロックが倚数あるずうたく構成されないこずです。 しかし、次の2぀の単玔なアクションを次々に実行したかっただけですが、実行の非同期により、すべおのカヌドが混乱したす。



Java 5でも過床の耇雑さを克服するために、Futureが登堎し、マルチスレッド蚈算のブロックをチェヌンで敎理できるようになりたした。 残念ながら、 java.util.concurrent.Futureクラスでは、銎染みのあるmapおよびflatMapメ゜ッドは芋぀かりたせん-モナドパタヌンを実装しおいたせん CompletableFutureの実装はこれに十分に近いですが。 したがっお、ここでも少しチヌトをしおJavaを超えお、読者にJava 8で登堎した堎合にFutureむンタヌフェヌスがどのようになるか想像する詊みを残したす。 Scala暙準ラむブラリのscala.concurrent.Futureトレむトむンタヌフェヌスを怜蚎しおくださいメ゜ッドシグネチャは倚少簡略化されおいたす。



 trait Future[+T] extends Awaitable[T] { def map[S](f: T => S): Future[S] def flatMap[S](f: T => Future[S]): Future[S] }
      
      





よく芋るず、メ゜ッドは非垞によく知られおいたす。 mapメ゜ッドは、枡された関数を将来の実行結果に適甚したす-この結果が利甚可胜になるずき。 さお、flatMapメ゜ッドは、それ自䜓が未来を返す関数を䜿甚したす。したがっお、これら2぀の先物はflatMapを䜿甚しお連鎖できたす。



 val f1 = Future { "Hello" + " world" } val f2 = { s: String => Future { s.length() } } Await.ready( f1.flatMap(f2) .map(println), 5.seconds )
      
      





それでは、蚈算の非同期盞互䟝存ブロックを実行する問題をどのように解決したしたか

  1. 問題は、オブゞェクトタむプ将来[String]で明確に瀺されたした。
  2. 圌らは、このタむプ内に補助コヌド党䜓前のチェヌンの最埌で次のチェヌンを呌び出すを隠したした。
  3. このタむプのオブゞェクトに䞀連の単玔なマッチングアクションを枡したしたfutur f2はこのタむプのオブゞェクトStringを受け取り、futur f1を返したす。


Future of Scalaもモナドパタヌンを実装しおいるず芁玄できたす。



たずめ



モナドは、安党でない補助コヌドのトンによっお分離される可胜性のあるアクションを簡単に構成チェヌンできるようにする機胜的なプログラミングパタヌンです。 䞊蚘の䟋に加えお、関数型蚀語では、モナドを䜿甚しお䟋倖的な状況を凊理し、I / O、デヌタベヌス、ステヌタスなどを凊理したす。 モナドパタヌンは、関数が最初のクラスのオブゞェクトである任意の蚀語で実装し倀ず芋なしたり、匕数ずしお枡すなど、Javaでも䞀郚の堎所で遭遇したすが、䞀郚の堎所では実装が望たれおいたす最高。



このトピックをより深く理解するには、次のリ゜ヌスをお勧めしたす。






All Articles