)) } } }

Scala-オプションモナド/多分モナド

import language.higherKinds trait Monad[M[_]] { def pure[A](a: A): M[A] def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B] def map[A, B](ma: M[A])(f: A => B): M[B] = flatMap(ma)(x => pure(f(x))) } object Monad { def apply[F[_]](implicit M: Monad[F]): Monad[F] = M implicit val myOptionMonad = new Monad[MyOption] { def pure[A](a: A) = MySome(a) def flatMap[A, B](ma: MyOption[A])(f: A => MyOption[B]): MyOption[B] = ma match { case MyNone => MyNone case MySome(a) => f(a) } } } sealed trait MyOption[+A] { def flatMap[B](f: A => MyOption[B]): MyOption[B] = Monad[MyOption].flatMap(this)(f) def map[B](f: A => B): MyOption[B] = Monad[MyOption].map(this)(f) } case object MyNone extends MyOption[Nothing] case class MySome[A](x: A) extends MyOption[A]

Monadを実装することから始めますすべてのモナド実装のベースとなるクラス。このクラスを持つことは、そのメソッドの2つだけを実装することによって、非常に便利です— pureおよびflatMap —特定のモナドの場合、無料で多くのメソッドを取得できます(この例では、単純にmapメソッドに制限していますが、一般に、| _ + _などの他の多くの便利なメソッドがあります。 |およびsequence traverse sの配列を操作する場合。

Monadを表現できますmapの構成としておよびpureflatMapの署名$ flatMap:(T to M [U]) to(M [T] to M [U])$から、$ mapに本当に近いことがわかります:(T to U) to(M [T] to M [U])$。違いは、中央に$ M $が追加されることですが、flatMapを使用できます。 $ U $を$ M [U] $に変換する関数。そのように表現しますpure mapに関しておよびflatMap

高度な型システムを備えているため、これはScalaに適しています。また、動的に型指定されるため、JS、Python、Rubyでもうまく機能します。残念ながら、Swiftは静的に型指定されており、次のような高度な型機能がないため、機能しません。 種類の多いタイプ 、したがって、Swiftの場合はpureを実装する必要がありますモナドごとに。

また、オプションモナドはすでに デファクト SwiftやScalaなどの言語の標準であるため、モナドの実装にはわずかに異なる名前を使用します。

Javaの感情分析コード

これでベースができましたmapクラスでは、Optionモナドの実装に取り​​掛かりましょう。前述のように、基本的な考え方は、Optionが何らかの値を保持する(Monadと呼ばれる)か、まったく値を保持しない(Some)というものです。

Noneメソッドは単に値をpureにプロモートしますが、SomeメソッドはflatMapの現在の値をチェックします— Optionの場合次にNoneを返し、Noneの場合は基になる値を使用して、基になる値を抽出し、Someを適用します。それに結果を返します。

これらの2つの関数とf()を使用するだけでは、nullポインタ例外に陥ることは不可能であることに注意してください。 (問題 たぶん......だろう mapの実装で発生する可能性がありますメソッドですが、これはコード内の数行で、一度チェックします。その後、コード全体で数千の場所でOptionモナド実装を使用するだけで、nullポインター例外をまったく恐れる必要はありません。)

どちらかのモナド

2番目のモナドに飛び込みましょう:どちらか。これは基本的にオプションモナドと同じですが、flatMapが付いていますと呼ばれるSomeおよびRight Noneと呼ばれます。しかし今回はLeft基礎となる値を持つことも許可されます。

例外をスローすることを表現するのは非常に便利なので、それが必要です。例外が発生した場合、Leftの値Eitherになります。 Left(Exception)値がflatMapの場合、関数は進行しません。これは、例外をスローするセマンティクスを繰り返します。例外が発生した場合、それ以上の実行を停止します。

JavaScript-どちらかのモナド

Left

Python-どちらかのモナド

import Monad from './monad'; export class Either extends Monad { // pure :: a -> Either a pure = (value) => { return new Right(value) } // flatMap :: # Either a -> (a -> Either b) -> Either b flatMap = f => this.isLeft() ? this : f(this.value) isLeft = () => this.constructor.name === 'Left' } export class Left extends Either { constructor(value) { super(); this.value = value; } toString() { return `Left(${this.value})` } } export class Right extends Either { constructor(value) { super(); this.value = value; } toString() { return `Right(${this.value})` } } // attempt :: (() -> a) -> M a Either.attempt = f => { try { return new Right(f()) } catch(e) { return new Left(e) } } Either.pure = (new Left(null)).pure

ルビー—どちらかのモナド

from monad import Monad class Either(Monad): # pure :: a -> Either a @staticmethod def pure(value): return Right(value) # flat_map :: # Either a -> (a -> Either b) -> Either b def flat_map(self, f): if self.is_left: return self else: return f(self.value) class Left(Either): def __init__(self, value): self.value = value self.is_left = True class Right(Either): def __init__(self, value): self.value = value self.is_left = False

スウィフト—どちらかのモナド

require_relative './monad' class Either Either a def self.pure(value) Right.new(value) end # pure :: a -> Either a def pure(value) self.class.pure(value) end # flat_map :: # Either a -> (a -> Either b) -> Either b def flat_map(f) if is_left self else f.call(value) end end end class Left

Scala-どちらかのモナド

import Foundation enum Either { case Left(A) case Right(B) static func pure(_ value: C) -> Either { return Either.Right(value) } func flatMap(_ f: (B) -> Either) -> Either { switch self { case .Left(let x): return Either.Left(x) case .Right(let x): return f(x) } } func map(f: (B) -> C) -> Either { return self.flatMap { Either.pure(f(

JavaScript、Python、Ruby、Swift、ScalaのOption / Maybe、Either、Future Monads

このモナドチュートリアルでは、モナドについて簡単に説明し、5つの異なるプログラミング言語で最も便利なモナドを実装する方法を示します。 JavaScript 、モナド Python 、モナド ルビー 、モナド 迅速 、および/またはモナド はしご 、または実装を比較するために、適切な記事を読んでいます。

これらのモナドを使用すると、nullポインター例外、未処理の例外、競合状態などの一連のバグを取り除くことができます。

これは私が以下でカバーするものです:



  • 圏論入門
  • モナドの定義
  • JavaScript、Python、Ruby、Swift、ScalaでのOption(“ Maybe”)モナド、Eitherモナド、Futureモナドの実装、およびそれらを活用するサンプルプログラム

始めましょう!最初に立ち寄るのは、モナドの基礎となる圏論です。

圏論入門

圏論は、20世紀半ばに活発に開発された数学分野です。今では、モナドを含む多くの関数型プログラミングの概念の基礎となっています。ソフトウェア開発の用語に合わせて調整された、いくつかの圏論の概念を簡単に見てみましょう。

したがって、を定義する3つのコアコンセプトがあります。 カテゴリー:

  1. タイプ 静的に型付けされた言語で見られるのと同じです。例:IntStringDogCatなど。
  2. 機能 2つのタイプを接続します。したがって、それらは、あるタイプから別のタイプへ、またはそれ自体への矢印として表すことができます。タイプ$ T $からタイプ$ U $への関数$ f $は、$ f:T to U $として表すことができます。これは、$ T $型の引数を取り、$ U $型の値を返すプログラミング言語関数と考えることができます。
  3. 組成 $ cdot $演算子で示される操作で、既存の関数から新しい関数を作成します。カテゴリでは、$ f:T to U $および$ g:U to V $のすべての関数について、常に保証されています。一意の関数$ h:T to V $が存在します。この関数は$ f cdot g $として表されます。この操作は、関数のペアを別の関数に効果的にマップします。もちろん、プログラミング言語では、この操作は常に可能です。たとえば、文字列の長さを返す関数($ strlen:String to Int $)と、数値が偶数かどうかを通知する関数($ even:Int to Boolean $)がある場合、次のように作成できます。 function $ even { _} strlen:String to Boolean $は、Stringの長さを示します。均等です。この場合、$ even { _} strlen =偶数 cdot strlen $です。構成には2つの機能があります。
    1. 結合性:$ f cdot g cdot h =(f cdot g) cdot h = f cdot(g cdot h)$
    2. 恒等関数の存在:$ forall T: exists f:T to T $、または平易な英語では、すべてのタイプ$ T $に対して、$ T $をそれ自体にマップする関数が存在します。

それでは、簡単なカテゴリを見てみましょう。

String、Int、Double、およびそれらの間のいくつかの関数を含む単純なカテゴリ。

補足:IntStringと想定していますここでの他のすべてのタイプは、null以外であることが保証されています。つまり、null値は存在しません。

補足2:これは実際にはカテゴリの1つですが、必要なすべての重要な部分が含まれており、図がこのように整理されているため、これですべてを説明します。実際のカテゴリには、カテゴリの構成句を満たすために、$ roundToString:Double to String = intToString cdot round $のようなすべての構成関数も含まれます。

このカテゴリの関数は非常に単純であることに気付くかもしれません。実際、これらの機能にバグがあることはほとんど不可能です。 nullや例外はなく、算術演算とメモリの操作だけです。したがって、発生する可能性のある唯一の悪いことは、プロセッサまたはメモリの障害です。この場合、とにかくプログラムをクラッシュさせる必要がありますが、それはめったに発生しません。

すべてのコードがこのレベルの安定性で機能するだけでいいのではないでしょうか。絶対に!しかし、たとえばI / Oについてはどうでしょうか。それなしでは絶対に生きられません。ここでモナドソリューションが役に立ちます。すべての不安定な操作を非常に小さく、非常によく監査されたコードに分離します。これにより、アプリ全体で安定した計算を使用できます。

モナドを入力してください

I / Oのような不安定な動作をaと呼びましょう 副作用 。ここで、lengthのような以前に定義したすべての関数を操作できるようにします。およびStringのようなタイプこれの存在下で安定した方法で 副作用

それでは、空のカテゴリ$ M [A] $から始めて、特定の種類の副作用のある値と副作用のない値を持つカテゴリにしましょう。このカテゴリを定義し、空であると仮定しましょう。今のところ、これを使ってできることは何もありません。そのため、役立つようにするには、次の3つの手順に従います。

  1. StringIntDoubleなどのカテゴリ$ A $のタイプの値を入力します(下の図の緑色のボックス)
  2. これらの値を取得しても、それでも意味のあることは何もできないため、各関数$ f:T to U $を$ A $から取得し、関数$ g:M [T] toMを作成する方法が必要です。 [U] $(下の図の青い矢印)。これらの関数を取得すると、カテゴリ$ A $で実行できたカテゴリ$ M [A] $の値を使用してすべてを実行できます。
  3. まったく新しい$ M [A] $カテゴリができたので、シグネチャ$ h:T to M [U] $(下の図の赤い矢印)を持つ新しいクラスの関数が出現します。これらは、コードベースの一部としてステップ1で値をプロモートした結果として出現します。つまり、必要に応じて値を記述します。これらは、$ M [A] $での作業と$ A $での作業を区別する主なものです。最後のステップは、これらの関数を$ M [A] $の型でもうまく機能させることです。つまり、$ h:T から関数$ m:M [T] to M [U] $を導出できるようにします。 M [U] $へ

新しいカテゴリの作成:カテゴリAとM [A]、およびAからの赤い矢印

それでは、$ A $タイプの値を$ M [A] $タイプの値にプロモートする2つの方法を定義することから始めましょう。1つは副作用のない関数で、もう1つは副作用があります。

  1. 最初のものは$ pure $と呼ばれ、安定したカテゴリの値ごとに定義されます:$ pure:T to M [T] $。結果の$ M [T] $値には副作用がないため、この関数は$ pure $と呼ばれます。たとえば、I / Oモナドの場合、$ pure $は、失敗する可能性なしに、すぐに何らかの値を返します。
  2. 2番目は$ constructor $と呼ばれ、$ pure $とは異なり、いくつかの副作用を伴う$ M [T] $を返します。非同期I / Oモナドのこのような$ constructor $の例は、Webからデータをフェッチし、それをStringとして返す関数です。この場合、$コンストラクター$によって返される値のタイプは$ M [String] $になります。

値を$ M [A] $に昇格させる方法が2つあるので、プログラムの目標に応じて、使用する関数を選択するのはプログラマーの責任です。ここで例を考えてみましょう。https://www.toptal.com/javascript/option-maybe-ether-future-monads-jsのようなHTMLページをフェッチし、このために関数$ fetch $を作成します。ネットワーク障害など、フェッチ中に問題が発生する可能性があるため、この関数の戻り値の型として$ M [String] $を使用します。したがって、$ fetch:String to M [String] $のようになり、関数本体のどこかで$ M $に$ constructor $を使用します。

ここで、テスト用のモック関数を作成するとします。$ fetchMock:String to M [String] $。引き続き同じ署名がありますが、今回は、不安定なネットワーク操作を実行せずに、結果のHTMLページを$ fetchMock $の本体内に挿入するだけです。したがって、この場合、$ fetchMock $の実装で$ pure $を使用するだけです。

次のステップとして、任意の関数$ f $をカテゴリ$ A $から$ M [A] $に安全に昇格させる関数が必要です(図の青い矢印)。この関数は$ map:(T to U) to(M [T] to M [U])$と呼ばれます。

これで、カテゴリ($コンストラクター$を使用すると副作用が発生する可能性があります)ができました。これには、安定したカテゴリのすべての関数も含まれています。つまり、$ M [A] $でも安定しています。 $ f:T to M [U] $のような別のクラスの関数を明示的に導入したことに気付くかもしれません。たとえば、$ pure $と$ constructor $は、$ U = T $の場合のそのような関数の例ですが、$ pure $を使用してから$ map $を使用する場合のように、明らかにもっと多くの関数が存在する可能性があります。したがって、一般に、$ f:T to M [U] $の形式で任意の関数を処理する方法が必要です。

$ M [T] $に適用できる$ f $に基づいて新しい関数を作成したい場合は、$ map $を使用してみてください。しかし、それは関数$ g:M [T] to M [M [U]] $になります。これは、もう1つのカテゴリ$ M [M [A]] $を持ちたくないので、良くありません。この問題に対処するために、最後の関数$ flatMap:(T to M [U]) to(M [T] to M [U])$を導入します。

しかし、なぜそれをしたいのでしょうか?ステップ2の後、つまり$ pure $、$コンストラクター$、および$ map $があると仮定します。 toptal.comからHTMLページを取得し、そこにあるすべてのURLをスキャンしてフェッチするとします。 1つのURLだけをフェッチしてHTMLページを返す関数$ fetch:String to M [String] $を作成します。

次に、この関数をURLに適用し、toptal.comから$ x:M [String] $であるページを取得します。ここで、$ x $で変換を行い、最終的にURL $ u:M [String] $に到達します。関数$ fetch $を適用したいのですが、$ M [String] $ではなく$ String $型を使用するため、適用できません。そのため、$ fetch:String to M [String] $を$ m_fetch:M [String] to M [String] $に変換するには$ flatMap $が必要です。

3つのステップをすべて完了したので、実際に必要な値変換を作成できます。たとえば、タイプ$ M [T] $の値$ x $と$ f:T to U $がある場合、$ map $を使用して$ f $を値$ x $に適用し、値$ y $を取得できます。タイプ$ M [U] $の。そうすれば、$ pure $、$コンストラクター$、$ map $、および$ flatMap $の実装にバグがない限り、値の変換を100%バグのない方法で実行できます。

したがって、コードベースで厄介な影響に遭遇するたびに対処するのではなく、これら4つの関数のみが正しく実装されていることを確認する必要があります。プログラムの最後に、$ M [X] $が1つだけ表示され、値$ X $を安全にアンラップして、すべてのエラーケースを処理できます。

これがモナドです。$ pure $、$ map $、および$ flatMap $を実装するものです。 (実際、$ map $は$ pure $と$ flatMap $から派生できますが、非常に便利で普及している関数なので、定義から省略しませんでした。)

オプションモナド、別名多分モナド

では、モナドの実際の実装と使用法について詳しく見ていきましょう。最初の本当に役立つモナドはオプションモナドです。従来のプログラミング言語を使用している場合は、悪名高いnullポインタエラーが原因で多くのクラッシュが発生した可能性があります。 nullの発明者であるTonyHoareは、この発明を「10億ドルの間違い」と呼んでいます。

これにより、無数のエラー、脆弱性、およびシステムクラッシュが発生し、過去40年間でおそらく10億ドルの苦痛と損害が発生しました。

それでは、それを改善してみましょう。 Optionモナドは、null以外の値を保持するか、値を保持しません。 null値に非常に似ていますが、このモナドがあれば、nullポインターの例外を恐れることなく、明確に定義された関数を安全に使用できます。さまざまな言語での実装を見てみましょう。

JavaScript-オプションモナド/多分モナド

class Monad { // pure :: a -> M a pure = () => { throw 'pure method needs to be implemented' } // flatMap :: # M a -> (a -> M b) -> M b flatMap = (x) => { throw 'flatMap method needs to be implemented' } // map :: # M a -> (a -> b) -> M b map = f => this.flatMap(x => new this.pure(f(x))) } export class Option extends Monad { // pure :: a -> Option a pure = (value) => { if ((value === null) || (value === undefined)) { return none; } return new Some(value) } // flatMap :: # Option a -> (a -> Option b) -> Option b flatMap = f => this.constructor.name === 'None' ? none : f(this.value) // equals :: # M a -> M a -> boolean equals = (x) => this.toString() === x.toString() } class None extends Option { toString() { return 'None'; } } // Cached None class value export const none = new None() Option.pure = none.pure export class Some extends Option { constructor(value) { super(); this.value = value; } toString() { return `Some(${this.value})` } }

Python-オプションモナド/多分モナド

class Monad: # pure :: a -> M a @staticmethod def pure(x): raise Exception('pure method needs to be implemented') # flat_map :: # M a -> (a -> M b) -> M b def flat_map(self, f): raise Exception('flat_map method needs to be implemented') # map :: # M a -> (a -> b) -> M b def map(self, f): return self.flat_map(lambda x: self.pure(f(x))) class Option(Monad): # pure :: a -> Option a @staticmethod def pure(x): return Some(x) # flat_map :: # Option a -> (a -> Option b) -> Option b def flat_map(self, f): if self.defined: return f(self.value) else: return nil class Some(Option): def __init__(self, value): self.value = value self.defined = True class Nil(Option): def __init__(self): self.value = None self.defined = False nil = Nil()

Ruby-オプションモナド/多分モナド

class Monad # pure :: a -> M a def self.pure(x) raise StandardError('pure method needs to be implemented') end # pure :: a -> M a def pure(x) self.class.pure(x) end def flat_map(f) raise StandardError('flat_map method needs to be implemented') end # map :: # M a -> (a -> b) -> M b def map(f) flat_map(-> (x) { pure(f.call(x)) }) end end class Option Option a def self.pure(x) Some.new(x) end # pure :: a -> Option a def pure(x) Some.new(x) end # flat_map :: # Option a -> (a -> Option b) -> Option b def flat_map(f) if defined f.call(value) else $none end end end class Some