JS関数式プログラミング-通信子と範疇論


前のいくつかの編では,関数式の比較的重要ないくつかの概念と,比較的複雑な論理を関数の組合せでどのように解決するかを紹介した.副作用をコントロールする方法を紹介する時だ.
データ型
最後の例を見てみましょう
const split = curry((tag, xs) => xs.split(tag))
const reverse = xs => xs.reverse()
const join = curry((tag, xs) => xs.join(tag))

const reverseWords = compose(join(''), reverse, split(''))

reverseWords('Hello,world!');

ここは実はreverseWordsはまだ読みにくいですが、彼が何をしているのか、戻り値は何なのか分かりません.コードを見に行かないと、最初は彼を使っていたとき、あなたは恐れていたはずです.「パラメータを1つ減らしたのではないでしょうか.パラメータを間違えたのではないでしょうか.戻り値は本当にずっと文字列ですか」.これもタイプシステムの重要性で、関数式を絶えず理解した後、関数式のプログラミングとタイプは密接に関連していることがわかります.ここでreverseWordsのタイプが明確に与えられると、ドキュメントに相当します.
しかし、JavaScriptはダイナミックタイプ言語であり、明確なタイプを指定することはできません.ただし、コメントにタイプを追加することはできます.
// reverseWords: string => string
const reverseWords = compose(join(''), reverse, split(''))
reverseWordsが受信文字列であることを指定し、文字列を返す関数に相当します.
JS自体は静的タイプ検出をサポートしていませんが、コミュニティにはFlowやType Scriptのようなタイプ検出をサポートするJSのスーパーセットがたくさんあります.もちろん、タイプ検出は、上記のドキュメントからのメリットだけでなく、プリコンパイル段階でエラーを早期に発見したり、動作を制約したりすることもできます.
もちろん私の後続の文章はJSを言語としていますが、注釈にタイプを付けます.
カテゴリ論に関する概念
範疇論は特に難しいわけではないが、抽象点の概念にすぎない.特に深く理解する必要はありません.関数式プログラミングの多くの概念は範疇論からマッピングされています.カテゴリ論に関する概念を理解することは,関数式プログラミングを理解するのに役立つ.また、私を信じて、あなたが小学校の中学校で一元関数と集合を学んだことがあれば、次のことを理解しても問題ありません.
定義#テイギ#
カテゴリの定義:
  • オブジェクトのセットであり、操作が必要なデータのセットである
  • である.
  • は、f:A->B
  • のようなデータオブジェクト上のマッピング関係のセットである.
  • 状態放射の組み合わせは、状態放射がいくつか組み合わせて新しい状態放射
  • を形成することができることである.
    画像の出典:https://en.wikipedia.org/wiki...
    簡単な例ですが、上図はウィキペディアから来ています.上は1つのカテゴリであり、合計3つのデータオブジェクトA,B,Cがあり、その後、fgは状態放射であり、gofは状態放射のセットである.簡単じゃないの?
    ここで,状態放射は関数として理解できるが,状態放射の組合せは関数の組合せとして理解できる.中のオブジェクトのセットは、同じ属性を持つデータセットではないでしょうか.
    通信子(functor)
    手紙は2つの範疇を関連付けるために使われる.
    画像の出典:https://ncatlab.org/nlab/show...
    上の図に対応して、例えばカテゴリCとDに対して、函子F:C=>Dは、C中の任意のオブジェクトXをD中のF(X)に変換することができる.Cの状態放射f:X=>YをDのF(f):F(X)=>F(Y)に変換します.手紙は次のことができます.
  • 変換オブジェクト
  • 遷移状態放射
  • 通信子を構築する(functor)
    Container
    前述したように,手紙は2つの範疇に関連付けることができる.カテゴリには必ずデータオブジェクトのセットがあります.ここでContainerを導入するのは、データ・オブジェクトを導入するためです.
    class Container {
      constructor (value) {
        this.$value = value
      }
      // (value) => Container(value)
      static of(value) {
        return new Container(value)
      }
    }

    Containerのクラスを宣言し,このContainerのインスタンスを生成するための静的ofメソッドを与えた.このofは実はもう一つのいい名前があって、関を売って、後で紹介します.
    このContainerを使用した例を見てみましょう.
    // Container(123)
    Container.of(123)
    
    // Container("Hello Conatiner!")
    Container.of("Hello Conatiner!")
    
    // Container(Conatiner("Test !"))
    Container.of(Container.of("Test !"))
    

    上に見たようにContainerはネストできます.このContaienrをよく見てみましょう
  • $valueのタイプは不確定ですが、値が付与されると
  • が決定されます.
  • 1 1つのConatinerには1つのvalue
  • しかありません.
  • 私たちは直接$valueを手に入れることができますが、そうしないでください.そうしないと、containerは何をしますか.
  • 最初のfunctor
    定義を振り返ってみましょう.手紙は2つのカテゴリを関連付けるために使用されます.2つのカテゴリを関連付けるには、状態放射(関数)が必要です.
    class Container {
      constructor (value) {
        this.$value = value
      }
      // (value) => Container(value)
      static of(value) {
        return new Container(value)
      }
      // (fn: x=>y) => Container(fn(value))
      map(fn) {
        return new Container(fn(this.$value))
      } 
    }

    まず、
    const concat = curry((str, xs) => xs.concat(str))
    const prop = curry((prop, xs) => xs[prop])
    
    // Container('TEST')
    Container.of('test').map(s => s.toUpperCase())
    
    // Container(10)
    Container.of('bombs').map(concat(' away')).map(prop('length')); 

    上のcurryが何なのか分からないので、2番目の文章を見てください.
    「ああ、これはあなたが言ったfunctorですが、何の役に立ちますか?」と言うかもしれません.次に、アプリケーションについてお話しします.
    しかし、アプリケーションの前にこのofについて話しますが、実は上のfunctorはpointed functorと呼ばれています.ES 5の中のArrayはこのモードを適用しています.Array.of.構築オブジェクトのnewキーワードを省略するだけでなく、モデルです.scalaの中のcompaion objectと少し似ているような気がします.
    Maybe type
    現実のコードでは、多くのデータがオプションで存在し、返されるデータが存在するか、存在しないかを選択できます.
    type Person = {
      info?: {
        age?: string
      }
    }

    上はflowのタイプ宣言で、?はこのデータが存在する可能性があり、存在しない可能性があります.上のデータ構造のように、バックエンドから返されたデータを受信するときによく遭遇すると信じています.もし私たちがこのageの属性を取るならば、私たちは通常どのように処理しますか?
    もちろん判断を加えました.
    const person = { info: {} }
    
    const getAge = (person) => {
      return person && person.info && person.info.age
    }
    
    getAge(person) // undefined

    ageを取るためには、多くの判断が必要であることに気づきます.データにオプションのデータがたくさんあると、コードがこのようなタイプの判断に満ちていることがわかります.心は疲れていますか.
    Okey、Maybe typeはこの問題を解決するために、まず一つ実現しましょう.
    
    class Maybe {
      static of(x) {
        return new Maybe(x);
      }
    
      get isNothing() {
        return this.$value === null || this.$value === undefined;
      }
    
      constructor(x) {
        this.$value = x;
      }
    
      map(fn) {
        return this.isNothing ? this : Maybe.of(fn(this.$value));
      }
    
      get() {
        if (this.isNothing) {
          throw new Error("Get Nothing")
        } else {
          return this.$value
        }
      }
    
      getOrElse(optionValue) {
        if (this.isNothing) {
          return optionValue
        } else {
          return this.$value
        }
      }
    }

    適用:
    
    type Person = {
      info?: {
        age?: string
      }
    }
    
    const prop = curry((tag, xs) => xs[tag])
    const map = curry((fn, f) => f.map(fn))
    
    const person = { info: {} }
    
    // safe get age
    Maybe.of(person.info).map(prop("age")) // Nothing
    
    // safe get age Point free style
    const safeInfo = xs => Maybe.of(person.info)
    const getAge = compose(map(prop('age')), safeInfo)
    getAge(person) // Nothing
    

    復刻すると、上のmapは依然としてfunctor(手紙)です.しかし、タイプ変換をするときに論理を加えました.
    map(fn) {
      return this.isNothing ? this : Maybe.of(fn(this.$value));
    }

    つまり、上記の変換関係は、
    実は図を見ると出てきて、「ああ、判断をmapの中に移動しました.何の役に立ちますか?」と.OK、メリットを羅列します.
  • より安全
  • は判断ロジックをカプセル化し、コードはより簡潔な
  • である.
  • 宣言コード、様々な判断がない
  • 実は、不確実性も副作用です.オプションのデータについては、実行時に彼の実際のデータ型を特定するのは難しいが、Maybeでカプセル化すること自体がカプセル化という不確実性である.これにより、私たちの1つのパラメータが出力を返す可能性があることを保証できます.