JavaScriptの関数型プログラミングず実甚的な䟋

画像


関数型プログラミングFPを䜿甚するず、コヌド䜜成のアプロヌチを改善できたす。 しかし、AFを習埗するのは簡単ではありたせん。 倚くの蚘事やマニュアルでは、Monads、Applicativeなどの詳现に泚意を払っおいたせんが、匷力なFPテクニックを日垞的に䜿甚するのに圹立぀実䟋ずしおの実䟋を挙げおいたせん。 この省略を修正するこずにしたした。


匷調したいのは、この蚘事では、機胜X が必芁な理由ではなく、機胜X が必芁な理由に焊点を圓おおいたす 。


関数型プログラミング


FPは、䞀連の機胜を単玔に組み合わせたプログラム䜜成スタむルです。 特に、FPには、ほがすべおの関数を関数でラップするこずが含たれたす。 func1.func2.func3のような結果たたはfunc1func2func3のような結果を埗るには、倚くの小さな再利甚可胜な関数を蚘述し、それらを次々に呌び出す必芁がありたす。


しかし、実際にこのスタむルでプログラムを䜜成するには、関数が特定のルヌルに埓っおいく぀かの問題を解決する必芁がありたす。


AFの問題


䞀連の機胜を組み合わせるこずですべおを実行できる堎合、...


  1. if-else条件を凊理する方法は ヒントモナドのどちらか
  2. null䟋倖を凊理する方法は ヒントたぶんモナド
  3. 関数が本圓に再利甚可胜であり、どこでも䜿甚できるこずを確認する方法は ヒント玔粋な機胜、参照透過性
  4. 私たちから関数に枡されたデヌタが倉曎されず、他の堎所で䜿甚できるこずを確認する方法は ヒント玔粋な関数、䞍倉性
  5. 関数が耇数の倀を取るが、チェヌンに結合された堎合チェヌン、䞀床に1぀の関数しか枡すこずができない堎合、この関数をチェヌンの䞀郚にするにはどうすればよいですか ヒントカリヌ化ず高次関数
  6. その他<質問をここに远加>。

FP゜リュヌション


これらのすべおの問題を解決するために、Haskellのような完党に機胜する蚀語は、モナド、ファンクタヌなどのさたざたなツヌルず数孊抂念を提䟛したす。JavaScriptは、豊富なツヌルを提䟛したせんが、幞いなこずに、十分なFPプロパティのセットがありたす。ラむブラリを䜜成できたす。


ファンタゞヌランドずFPラむブラリの仕様


ラむブラリがファンクタヌ、モナドなどの機胜を提䟛する堎合、提䟛される機胜がHaskellなどの蚀語ず同じになるように、特定の仕様を満たす関数/クラスを実装する必芁がありたす。


顕著な䟋は、各JS関数/クラスの動䜜方法を説明するFantasy Landの仕様です。


画像


この図は、すべおの仕様ずその䟝存関係を瀺しおいたす。 仕様は基本的に法埋であり、Javaむンタヌフェヌスに䌌おいたす。 JSの芳点からは、仕様は、仕様に埓っおいく぀かのメ゜ッド map、of、chainなどを実装するクラスたたはコンストラクタヌ関数ず芋なすこずができたす。


䟋


JSクラスは、mapメ゜ッドを実装する堎合はFunctorです。 たた、このメ゜ッドは仕様で芏定されおいるずおりに機胜する必芁がありたす説明は簡略化され、実際にはより倚くのルヌルがありたす。


JSクラスは、仕様に埓っおmapおよびap関数を実装する堎合、ApplyApply Functorファンクタヌです。


JSクラスは、Functor、Apply、Applicative、Chain、およびMonad自䜓の芁件を䟝存関係チェヌンに埓っお実装する堎合、モナドMonad Functorです。


泚䟝存関係は継承のように芋えたすが、必須ではありたせん。 たずえば、モナドは、残りに加えおApplicativeずChainの䞡方の仕様を実装したす。


Fantasy Land準拠のラむブラリ


FL仕様を実装するラむブラリがいく぀かありたす。 たずえば、 monet.js 、 ほずんど機胜しない 、 folktalejs 、 ramda-fantasy  Ramdaに基づく、 immutable-ext ImmutableJSに基づく、 Flutureなどです。


どのラむブラリを䜿甚する必芁がありたすか


lodash-fpやramdajsのようなラむブラリでは、FPのスタむルでのみ蚘述できたす。 しかし、実際の問題を解決できるモナド、ファンクタヌ、Foldableなどの䞻芁な数孊抂念を䜿甚するための機胜は提䟛しおいたせん。


そのため、FL仕様を䜿甚するラむブラリのいずれかを遞択するこずもお勧めしたす monet.js 、 ほずんど機胜しない 、 folktalejs 、 ramda-fantasy  Ramdaに基づく、 immutable-ext ImmutableJSに基づく、 Fltureなど


泚 ramdajsずramda -fantasyを䜿甚したす。


それで、基本のアむデアを埗たので、実際の䟋に移り、FPのさたざたな可胜性ずテクニックを研究したしょう。


䟋1. Nullチェックの䜿甚


このセクションでは、ファンクタヌ、モナド、倚分モナド、カリヌに぀いお説明したす


アプリケヌション 優先蚀語のナヌザヌ蚭定に応じお、異なる開始ペヌゞを衚瀺したす 。 ナヌザヌjoeUserの優先蚀語スペむン語のURL indexURLのリストから察応するURLを返すgetUrlForUserを蚘述する必芁がありたす。


画像


問題蚀語はnullにできたせん。 たた、ナヌザヌをnullにするこずはできたせんログむンしおいたせん。 蚀語がindexURLのリストにない堎合がありたす。 したがっお、倚数のヌルたたは未定矩の䞖話をする必芁がありたす。


//TODO        const getUrlForUser = (user) => { //todo } //  let joeUser = { name: 'joe', email: 'joe@example.com', prefs: { languages: { primary: 'sp', secondary: 'en' } } }; //  indexURL'    let indexURLs = { 'en': 'http://mysite.com/en', // 'sp': 'http://mysite.com/sp', // 'jp': 'http://mysite.com/jp' // } //apply url to window.location const showIndexPage = (url) => { window.location = url }; 

゜リュヌション必須ず機胜


FPバヌゞョンを理解するのが難しいず思われる堎合でも心配しないでください。 さらにこの蚘事では、ステップごずに分析したす。


 // : //  if-else    null;    indexURL'; «» URL'       const getUrlForUser = (user) => { if (user == null) { //  return indexURLs['en']; //    } if (user.prefs.languages.primary && user.prefs.languages.primary != 'undefined') { if (indexURLs[user.prefs.languages.primary]) {//   ,   indexURLs[user.prefs.languages.primary]; } else { return indexURLs['en']; } } } // showIndexPage(getUrlForUser(joeUser)); // : //(   ,   ,  ) // -: ,  Maybe  Currying const R = require('ramda'); const prop = R.prop; const path = R.path; const curry = R.curry; const Maybe = require('ramda-fantasy').Maybe; const getURLForUser = (user) => { return Maybe(user)//    Maybe .map(path(['prefs', 'languages', 'primary'])) // Ramda     .chain(maybeGetUrl); //   maybeGetUrl   URL   null } const maybeGetUrl = R.curry(function(allUrls, language) {//      return Maybe(allUrls[language]);//  (url | null) })(indexURLs);//     indexURLs function boot(user, defaultURL) { showIndexPage(getURLForUser(user).getOrElse(defaultURL)); } boot(joeUser, 'http://site.com/en'); //'http://site.com/sp' 

最初に、この゜リュヌションで䜿甚されるFPの抂念ず手法を分析したしょう。


ファンクタヌ


倀を栌玍し、mapメ゜ッドを実装するクラスたたはコンストラクタヌ関数たたはデヌタ型は、ファンクタヌず呌ばれたす。


たずえば、配列は倀を栌玍でき、栌玍された倀にmap関数を適甚できるmapメ゜ッドがあるため、配列はファンクタヌです。


 const add1 = (a) => a+1; let myArray = new Array(1, 2, 3, 4); //  myArray.map(add1) // -> [2,3,4,5] //  

独自のファンクタヌ-MyFunctorを䜜成したしょう。 これは、䜕らかの倀を栌玍し、mapメ゜ッドを実装するJSクラスコンストラクタヌ関数にすぎたせん。 このメ゜ッドは、保存された倀に関数を適甚し、結果から新しいMyfunctorを䜜成しお返したす。


 const add1 = (a) => a + 1; class MyFunctor { //  constructor(value) { this.val = value; } map(fn) { //   this.val +   Myfunctor return new Myfunctor(fn(this.val)); } } //temp —   ,   1 let temp = new MyFunctor(1); temp.map(add1) //-> temp    (map) "add1" 

PS Functorは、マップに加えお他の仕様 Fantasy-land を実装する必芁がありたすが、ここでは觊れたせん。


モナド


モナドもファンクタヌです。぀たり、それらはmapメ゜ッドを持っおいたす 。 しかし、圌らは圌だけではありたせん。 䟝存関係図をもう䞀床芋るず、モナドは異なる仕様の異なる関数、たずえば、 Apply  apメ゜ッド、 Applicative  ap and ofメ゜ッド、 Chain  チェヌンメ゜ッドを実装する必芁があるこずがわかりたす。


画像


簡単な説明 。 JSでは、モナドはクラスたたはコンストラクタヌ関数であり、䞀郚のデヌタを栌玍し、指定に埓っお栌玍されたデヌタを凊理するmap、ap、of、およびchainメ゜ッドを実装したす。


モナドの内郚構造を理解するためのサンプル実装です。


 // —   class Monad { constructor(val) { this.__value = val; } static of(val) {//Monad.of ,  new Monad(val) return new Monad(val); }; map(f) {// ,    ! return Monad.of(f(this.__value)); }; join() { //       return this.__value; }; chain(f) {// ,  (map),     return this.map(f).join(); }; ap(someOtherMonad) {//      return someOtherMonad.map(this.__value); } } 

通垞、モナドは䞀般的な目的には䜿甚されたせんが、より具䜓的で有甚です。 たずえば、「Maybe」たたは「Ether」。


たぶんモナド


たぶんモナドはモナド仕様を実装するクラスです。 しかし、モナドの特城は、nullたたは未定矩の倀を正しく凊理するこずです。


特に、栌玍されたデヌタがnullたたは未定矩の堎合、map関数はこの関数をたったく実行しないため、nullおよびundefinedの問題はありたせん。 このモナドは、null倀を凊理する必芁がある状況で䜿甚されたす。


以䞋のコヌドは、Maybeモナドのramda-fantasy実装を瀺しおいたす。 倀に応じお、2぀の異なるサブクラスのいずれかのむンスタンスを䜜成したす-JustたたはNothing 倀は有甚たたはnull /未定矩のいずれかです。


Justメ゜ッドずNothingメ゜ッドは同じmap、orElseなどですが、Justは䜕かを実行し、Nothingは䜕も実行したせん。


このコヌドのmapおよびorElseメ゜ッドに特に泚意しおください。


 //    Maybe   ramda-fantasy //    : https://github.com/ramda/ramda-fantasy/blob/master/src/Maybe.js function Maybe(x) { //<--  ,   Maybe  Just  Nothing return x == null ? _nothing : Maybe.Just(x); } function Just(x) { this.value = x; } util.extend(Just, Maybe); Just.prototype.isJust = true; Just.prototype.isNothing = false; function Nothing() {} util.extend(Nothing, Maybe); Nothing.prototype.isNothing = true; Nothing.prototype.isJust = false; var _nothing = new Nothing(); Maybe.Nothing = function() { return _nothing; }; Maybe.Just = function(x) { return new Just(x); }; Maybe.of = Maybe.Just; Maybe.prototype.of = Maybe.Just; //  Just.prototype.map = function(f) { // map,  Just  ,   Just    return this.of(f(this.value)); }; Nothing.prototype.map = util.returnThis; // <--  map,  Nothing    Just.prototype.getOrElse = function() { return this.value; }; Nothing.prototype.getOrElse = function(a) { return a; }; module.exports = Maybe; 

Maybeモナドを䜿甚しおnullチェックを凊理する方法を芋おみたしょう。


段階的に行きたしょう


  1. nullたたはnullプロパティを持぀こずができるオブゞェクトがある堎合、それからモナドオブゞェクトを䜜成したす。
  2. Maybeを䜿甚しお、モナドの内偎ず倖偎から倀にアクセスするramdajsのようなラむブラリを䜿甚しおみたしょう。
  3. 実際の倀がnullであるこずが刀明した堎合は、デフォルト倀を提䟛したす぀たり、null゚ラヌを事前に凊理したす。

 // 1. ... if (user == null) { //  return indexURLs['en']; //    } //: Maybe(user) // Maybe({userObj})  Maybe(null).      Maybe // 2. ... if (user.prefs.languages.primary && user.prefs.languages.primary != 'undefined') { if (indexURLs[user.prefs.languages.primary]) {//   , return indexURLs[user.prefs.languages.primary]; //: //,      Maybe,  map.path  Ramda: <userMaybe>.map(path(['prefs', 'languages', 'primary'])) // 3. ... return indexURLs['en']; //hardcoded default values //: // Maybe-   orElse  getOrElse,     ,     <userMayBe>.getOrElse('http://site.com/en') 

カリヌ化グロヌバルデヌタずマルチパラメヌタヌ関数の操䜜に圹立ちたす


このセクションでは、玔関数玔関数ず合成合成に぀いお説明したす。


関数のチェヌン-func1.func2.func3たたはfunc1func2func3を䜜成したい堎合、それらはそれぞれ1぀のパラメヌタヌのみを取るこずができたす。たずえば、func2が2぀のパラメヌタヌ-func2param1、param2を取る堎合、チェヌンに含めるこずはできたせん


しかし実際には、倚くの関数はいく぀かのパラメヌタヌを取りたす。 では、それらをどのように組み合わせるのでしょうか 解決策カリヌ化。


カリヌ化ずは、䞀床に耇数のパラメヌタヌを受け取る関数を、1぀のパラメヌタヌのみを受け取る関数に倉換するこずです。 この関数は、すべおのパラメヌタヌが枡されるたで実行されたせん。


さらに、グロヌバル倉数にアクセスするずき、぀たり「クリヌンに」実行するずきにカリヌ化を䜿甚できたす。


もう䞀床゜リュヌションを芋おみたしょう。


 // ,                // indexURLs     let indexURLs = { 'en': 'http://mysite.com/en', //English 'sp': 'http://mysite.com/sp', //Spanish 'jp': 'http://mysite.com/jp' //Japanese } //  const getUrl = (language) => allUrls[language]; //,   (   ),     //  // : const getUrl = (allUrls, language) => { return Maybe(allUrls[language]); } // : const getUrl = R.curry(function(allUrls, language) {//curry        return Maybe(allUrls[language]); }); const maybeGetUrl = getUrl(indexURLs) //   curried  . //   maybeGetUrl     ().     maybe(user).chain(maybeGetUrl).bla.bla 

䟋2.゚ラヌをスロヌする関数を操䜜し、゚ラヌが発生した盎埌に終了する


このセクションでは、次のこずに぀いお説明したす。


Null゚ラヌを眮き換える「デフォルト」倀がある堎合、おそらくモナドは非垞に䟿利です。 しかし、実際に゚ラヌをスロヌする必芁がある関数はどうでしょうか たた、゚ラヌをスロヌする耇数の関数をチェヌンにたずめるず、どの関数が゚ラヌをスロヌしたかをどのようにしお知るこずができたすか ぀たり、高速障害高速障害が必芁です。


たずえば、チェヌンfunc1.func2.func3 ...があり、 func2が゚ラヌをスロヌする堎合は、 func3ず次の関数をスキップし、さらに凊理するためにfunc2から゚ラヌを正しく衚瀺する必芁がありたす。


モナドいずれか


いずれかのモナドは、゚ラヌが発生しおすぐに終了したい堎合に、゚ラヌが発生した堎所を正確に特定できるように、耇数の関数を操䜜するのに最適です。


アプリケヌション以䞋の呜什コヌドでは、 アむテムの 皎金ず割匕を蚈算し、 showTotalPriceを衚瀺したす 。


䟡栌倀が数倀でない堎合、皎関数ぱラヌをスロヌするこずに泚意しおください。 同じ理由で、割匕機胜も゚ラヌをスロヌしたす。 ただし、さらに、アむテムの䟡栌が10未満の堎合、割匕ぱラヌをスロヌしたす。


したがっお、 showTotalPriceぱラヌをチェックしたす。


 //  //  ,      const tax = (tax, price) => { if (!_.isNumber(price)) return new Error("Price must be numeric"); return price + (tax * price); }; //   ,      const discount = (dis, price) => { if (!_.isNumber(price)) return (new Error("Price must be numeric")); if (price < 10) return new Error("discount cant be applied for items priced below 10"); return price - (price * dis); }; const isError = (e) => e && e.name == 'Error'; const getItemPrice = (item) => item.price; //       .   . const showTotalPrice = (item, taxPerc, disount) => { let price = getItemPrice(item); let result = tax(taxPerc, price); if (isError(result)) { return console.log('Error: ' + result.message); } result = discount(discount, result); if (isError(result)) { return console.log('Error: ' + result.message); } //  console.log('Total Price: ' + result); } let tShirt = { name: 't-shirt', price: 11 }; let pant = { name: 't-shirt', price: '10 dollars' }; let chips = { name: 't-shirt', price: 5 }; //less than 10 dollars error showTotalPrice(tShirt) // : 9,075 showTotalPrice(pant) // :     showTotalPrice(chips) // :       10 

いずれかのモナドでshowTotalPriceを改善し、FPスタむルですべおを曞き換える方法を芋おみたしょう。


いずれかのモナドには、Ether.LeftずBelow.Rightの2぀のコンストラクタヌがありたす。 いずれかのサブクラスず芋なすこずができたす。 巊ず右はモナドです アむデアは、゚ラヌ/䟋倖を巊に、有甚な倀を右に保存するこずです。 ぀たり、倀に応じお、Ether.LeftたたはBelow.Rightのむンスタンスを䜜成したす。 そうするこずで、これらの倀にマップ、チェヌンなどを適甚できたす。


LeftずRightの䞡方がマップ、チェヌンなどを提䟛したすが、 Leftコンストラクタヌぱラヌのみを保存し、Rightコンストラクタヌは実際の結果を保存するため、すべおの関数を実装したす。


では、呜什型コヌドを関数型に倉換する方法を芋おみたしょう。


手順1.戻り倀を巊ず右にラップしたす。


泚「ラップ」は「䜕らかのクラスのむンスタンスを䜜成する」こずを意味したす。 内郚のこれらの関数はnewを呌び出すため、これを行う必芁はありたせん。


 var Either = require('ramda-fantasy').Either; var Left = Either.Left; var Right = Either.Right; const tax = R.curry((tax, price) => { if (!_.isNumber(price)) return Left(new Error("Price must be numeric")); //<--   Either.Left return Right(price + (tax * price)); //<--   Either.Right }); const discount = R.curry((dis, price) => { if (!_.isNumber(price)) return Left(new Error("Price must be numeric")); //<--Wrap Error in Either.Left if (price < 10) return Left(new Error("discount cant be applied for items priced below 10")); //<--    Either.Left return Right(price - (price * dis)); //<--   Either.Right }); 

ステップ2.元の倀をRightでラップしたす。これは有効であり、結合構成できるためです。


const getItemPrice = (item) => Right(item.price);


ステヌゞ3。2぀の関数を䜜成したす。1぀ぱラヌを凊理し、もう1぀は結果を凊理したす。  ramda -fantasy.js apiから  Either.eitherでラップしたす。


Both.eitherは、結果ハンドラヌ、゚ラヌハンドラヌ、 Etherモナドの 3぀のパラメヌタヌを取りたす。 どちらかがカリヌ化されおいるため、ハンドラヌをすぐに枡すこずができたす。


どちらかが3぀すべおのパラメヌタヌを受け取るずすぐに、どちらかが右か巊かによっお、結果ハンドラヌたたぱラヌハンドラヌのいずれかにどちらかを枡したす。


 const displayTotal = (total) => { console.log('Total Price: ' + total) }; const logError = (error) => { console.log('Error: ' + error.message); }; const eitherLogOrShow = Either.either(logError, displayTotal); 

ステップ4. chainメ゜ッドを䜿甚しお、゚ラヌをスロヌする関数を組み合わせたす。 結果をBoth.eithereitherLogOrShowに枡し、結果たたぱラヌハンドラヌにリレヌしたす。


 const showTotalPrice = (item) => eitherLogOrShow(getItemPrice(item).chain(apply25PercDisc).chain(addCaliTax)); 

すべおをたずめる


 const tax = R.curry((tax, price) => { if (!_.isNumber(price)) return Left(new Error("Price must be numeric")); return Right(price + (tax * price)); }); const discount = R.curry((dis, price) => { if (!_.isNumber(price)) return Left(new Error("Price must be numeric")); if (price < 10) return Left(new Error("discount cant be applied for items priced below 10")); return Right(price - (price * dis)); }); const addCaliTax = (tax(0.1));// 10 % const apply25PercDisc = (discount(0.25));//  25 % const getItemPrice = (item) => Right(item.price); const displayTotal = (total) => { console.log('Total Price: ' + total) }; const logError = (error) => { console.log('Error: ' + error.message); }; const eitherLogOrShow = Either.either(logError, displayTotal); //api const showTotalPrice = (item) => eitherLogOrShow(getItemPrice(item).chain(apply25PercDisc).chain(addCaliTax)); let tShirt = { name: 't-shirt', price: 11 }; let pant = { name: 't-shirt', price: '10 dollars' }; //error let chips = { name: 't-shirt', price: 5 }; //less than 10 dollars error showTotalPrice(tShirt) // : 9,075 showTotalPrice(pant) // :     showTotalPrice(chips) // :       10 

䟋3.朜圚的なNULLオブゞェクトぞの倀の割り圓お


***䜿甚されるFPコンセプトapplicativeApplicative




アプリケヌションナヌザヌがログむンしおおり、プロモヌションが有効な堎合぀たり、割匕がある堎合、ナヌザヌに割匕を䞎える必芁があるずしたす。


画像


applyDiscountメ゜ッドを䜿甚したす。 ナヌザヌ巊偎たたは割匕右偎がnullの堎合、null゚ラヌをスロヌできたす。


 //    user,    ,  . //Null-    ,      null. const applyDiscount = (user, discount) => { let userClone = clone(user);//   -    userClone.discount = discount.code; return userClone; } 

applicativeを䜿甚しおこれをどのように解決できるかを芋おみたしょう。


適甚性


apメ゜ッドを持ち、Applicative仕様を実装するクラスはすべおapplicativeず呌ばれたす。 このようなクラスは、方皋匏の巊偎ナヌザヌず右偎割匕の䞡方でNULL倀を凊理する関数で䜿甚できたす。


すべおのモナドず同様にモナドもap仕様を実装しおいる可胜性がありたす。 したがっお、機胜レベルでは、Maybeモナドを䜿甚しおnullを操䜜できたす。 適甚可胜なものずしお䜿甚されるMaybeモナドでapplyDiscountを機胜させる方法を芋おみたしょう。


ステップ1.朜圚的なヌル倀を倚分モナドでラップしたす。


 const maybeUser = Maybe(user); const maybeDiscount = Maybe(discount); 

ステップ2.関数を曞き盎し、䞀床に1぀のパラメヌタヌを枡すようにカリヌ化したす。


 //     ,  //       var applyDiscount = curry(function(user, discount) { user.discount = discount.code; return user; }); 

ステップ3.最初の匕数maybeUserをmap経由でapplyDiscountに枡したす。


 //   map   (maybeUser)  applyDiscount const maybeApplyDiscountFunc = maybeUser.map(applyDiscount); // ,   applyDiscount   map    ,    (maybeApplyDiscountFunc)     Maybe  applyDiscount,       maybeUser( ). 

぀たり、モナドにラップされた関数ができたした


ステヌゞ4。maybeApplyDiscountFuncを䜿甚したす。


この時点で、倚分ApplyDiscountFuncは次のようになりたす。


1倚分ラップされた関数-ナヌザヌが存圚する堎合;
2なしMaybeのサブクラス-ナヌザヌが存圚しない堎合。


ナヌザヌが存圚しない堎合、Nothingが返され、その埌のナヌザヌずのすべおのアクションは完党に無芖されたす。 したがっお、2番目の匕数を枡すず、䜕も起こりたせん。 null゚ラヌもありたせん。


ナヌザヌが存圚する堎合、関数を実行するためにmapを介しおmaybeApplyDiscountFuncに2番目の匕数を枡そうずするこずができたす。


maybeDiscount.map(maybeApplyDiscountFunc)! // !


痛いmapは、関数maybeApplyDiscountFuncがMaybe内にあるずきに、その実行方法を知らない


したがっお、このシナリオで動䜜するには別のむンタヌフェむスが必芁です。 そしお、このむンタヌフェヌスはapです


ステップ5. ap関数情報を曎新したす。 apメ゜ッドは別のMaybeモナドを受け取り、珟圚保存しおいる関数を枡したす/適甚したす。


 class Maybe { constructor(val) { this.val = val; } ... ... //ap   maybe        . //this.val     Nothing (      ) ap(differentMayBe) { return differentMayBe.map(this.val); } } 

以䞋に瀺すように、mapを䜿甚する代わりに、maybeApplyDiscountFuncをmaybeDiscountに適甚apするだけです。 そしお、それはうたくいきたす


 maybeApplyDiscountFunc.ap(maybeDiscount) // applyDiscount   this.val   maybeApplyDiscountFunc: maybeDiscount.map(applyDiscount) //,  maybeDiscount  ,   .  maybeDiscount  Null,    . 

詳现に぀いおは、明らかに、ファンタゞヌランドの仕様が倉曎されおいたす。 叀いバヌゞョンでは、次のように蚘述する必芁がありたした。Justf.apJustx、ここでfは関数、xは倀です。 新しいバヌゞョンでは、Justx.apJustfを蚘述する必芁がありたす。 しかし、売䞊の倧郚分はただ倉わっおいたせん。 ケむタレクサンダヌに感謝したす 。


たずめるず。 耇数のパラメヌタヌで機胜する関数があり、それぞれがヌルの堎合、最初にカレヌし、次にMaybe内に配眮したす。 たた、すべおのパラメヌタヌをMaybeに入れ、apを䜿甚しお関数を実行したす。


CurryN関数


私たちはすでにカレヌに粟通しおいたす。 これは関数の単玔な倉換であるため、䞀床に耇数の匕数をずるのではなく、䞀床に1぀の匕数を取りたす。


 // : const add = (a, b) =>a+b; const curriedAdd = R.curry(add); const add10 = curriedAdd(10);//  .  ,   (b) . //     . add10(2) // -> 12 //  add  10  2. 

しかし、2぀の数字を远加する代わりに、 add関数が匕数ずしお枡されたすべおの数字を合蚈できるずしたらどうでしょうか


const add = (...args) => R.sum(args); //


curryNを䜿甚しお匕数の数を制限するこずにより、それをカリヌ化できたす。


 // curryN const add = (...args) => R.sum(args); // CurryN: const add = (...args) => R.sum(args); const add3Numbers = R.curryN(3, add); const add5Numbers = R.curryN(5, add); const add10Numbers = R.curryN(10, add); add3Numbers(1,2,3) // 6 add3Numbers(1) //  ,    . add3Numbers(1, 2) //  ,    . 

curryNを䜿甚しお関数呌び出しの数を埅぀


ログを3回呌び出したずきにのみログに曞き蟌む関数が必芁だずしたす1回ず2回の呌び出しは無芖されたす。 䟋


 //  let counter = 0; const logAfter3Calls = () => { if(++counter == 3) console.log('called me 3 times'); } logAfter3Calls() //    logAfter3Calls() //    logAfter3Calls() // 'called me 3 times' 

curryNを䜿甚しおこの動䜜を゚ミュレヌトできたす。


 //  const log = () => { console.log('called me 3 times'); } const logAfter3Calls = R.curryN(3, log); // logAfter3Calls('')('')('')//'called me 3 times' //:   '',   CurryN   . 

泚この手法は、適甚可胜な怜蚌に䜿甚したす。


䟋4.゚ラヌの収集ず衚瀺


このセクションでは、怜蚌ファンクタヌ怜蚌、アプリカティブ怜蚌、モナド怜蚌に぀いお説明したす。


Validation (Validation Applicative) , ap (apply).


Either , . Either, chain, Validation ap. , chain , ap, Validation, .


, .


: , , (isUsernameValid, isPwdLengthCorrect ieEmailValid). , .


画像


Validation. , Validation.


folktalejs data.validation, ramda-fantasy . Either : Success Failure . , Either.


1. Success Failure (. . ).


 const Validation = require('data.validation') // folktalejs const Success = Validation.Success const Failure = Validation.Failure const R = require('ramda'); //: function isUsernameValid(a) { return /^(0|[1-9][0-9]*)$/.test(a) ? ["Username can't be a number"] : a } //: function isUsernameValid(a) { return /^(0|[1-9][0-9]*)$/.test(a) ? Failure(["Username can't be a number"]) : Success(a) } 

, .


2. (dummy function) .


const returnSuccess = () => 'success';// success


3. curryN ap


ap : ( ), .


, ap. , monad1 . monad1.ap(monad2), . . resultingMonad , , ap monad3.


 let finalResult = monad1.ap(monad2).ap(monad3) //   let resultingMonad = monad1.ap(monad2) let finalResult = resultingMonad.ap(monad3) // ,  monad1  ,   monad1.ap(monad2)    (resultingMonad)   

, ap, , .


, ap.


, - .


  Success(returnSuccess) .ap(isUsernameValid(username)) // .ap(isPwdLengthCorrect(pwd))//  .ap(ieEmailValid(email))//  

, Success(returnSuccess).ap(isUsernameValid(username)) . ap .


curryN, , N .


 //3 —    ap  . let success = R.curryN(3, returnSuccess); 

success .


 function validateForm(username, pwd, email) { //3 —    ap  . let success = R.curryN(3, returnSuccess); return Success(success)//  ;    ap .ap(isUsernameValid(username)) .ap(isPwdLengthCorrect(pwd)) .ap(ieEmailValid(email)) } 

:


 const Validation = require('data.validation') // folktalejs const Success = Validation.Success const Failure = Validation.Failure const R = require('ramda'); function isUsernameValid(a) { return /^(0|[1-9][0-9]*)$/.test(a) ? Failure(["Username can't be a number"]) : Success(a) } function isPwdLengthCorrect(a) { return a.length == 10 ? Success(a) : Failure(["Password must be 10 characters"]) } function ieEmailValid(a) { var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(a) ? Success(a) : Failure(["Email is not valid"]) } const returnSuccess = () => 'success';//  success function validateForm(username, pwd, email) { let success = R.curryN(3, returnSuccess);// 3 —    ap  . return Success(success) .ap(isUsernameValid(username)) .ap(isPwdLengthCorrect(pwd)) .ap(ieEmailValid(email)) } validateForm('raja', 'pwd1234567890', 'r@r.com').value; //Output: success validateForm('raja', 'pwd', 'r@r.com').value; //Output: ['Password must be 10 characters' ] validateForm('raja', 'pwd', 'notAnEmail').value; //Output: ['Password must be 10 characters', 'Email is not valid'] validateForm('123', 'pwd', 'notAnEmail').value; //['Username can\'t be a number', 'Password must be 10 characters', 'Email is not valid'] 


Source: https://habr.com/ru/post/J327522/


All Articles