banner
biuaxia

biuaxia

"万物皆有裂痕,那是光进来的地方。"
github
bilibili
tg_channel

基于パイプライン技術による関数のストリーム呼び出し

title: パイプライン技術を使用した関数のストリーム呼び出しの実現
date: 2022-07-04 10:40:00
toc: false
index_img: http://api.btstu.cn/sjbz/?lx=m_dongman&cid=10
category:

  • Go
    tags:
  • 順序
  • 接続
  • サポート
  • リクエスト
  • メソッド
  • 関数

パイプライン(Pipeline)という用語は、Unix のシェルコマンドから派生しており、単純なコマンドを組み合わせることで強力な機能を実現することができます。たとえば、システムのプロセスリストから nginx プロセスをフィルタリングしたい場合、次のようにします:

ps -ef | grep nginx 

ここでは、ps と grep という 2 つの基本的な Unix コマンドをパイプで接続し、ps -ef の結果に対して grep nginx で nginx プロセスをフィルタリングしています。

関数型プログラミングでは、パイプラインの考え方を活用して、いくつかの単純な関数を連結してより強力な機能を構築することもできます。最も一般的なのは、ストリーム関数呼び出し(水のように、オブジェクト指向プログラミングではストリームインターフェースパターンに対応し、チェーン処理を実現できます)です。

これにより、各関数は自分が処理することに専念し、それを最大限に活用し、組み合わせ(パイプライン)によってより複雑なビジネス機能を構築することができます。これは、SOLID 設計原則の単一責任原則に合致しています。

私たちは、main 関数でMap-Reduce-Filter機能モジュールをパイプラインで組み合わせ、これらの関数のストリーム呼び出しを実現しています:

package main

import (
  "fmt"
  "log"
)

type user struct {
  name string
  age  int
}

func main() {
  var users = []user{
    {
      name: "xmp",
      age:  11,
    },
    {
      name: "wlm",
      age:  24,
    },
    {
      name: "wjf",
      age:  30,
    },
    {
      name: "lm",
      age:  50,
    },
  }
  // プロセッサ関数は宣言された順序で順次呼び出されます
  result := sumAge(users, filterAge, mapAgeToSlice)

  fmt.Println("総年齢(40より大きく20より小さいものを除く):", result)
}

func filterAge(users []user) interface{} {
  var result []user
  for _, user := range users {
    if user.age < 40 && user.age > 20 {
      result = append(result, user)
    }
  }
  return result
}

func mapAgeToSlice(users []user) interface{} {
  var result []int
  for _, user := range users {
    result = append(result, user.age)
  }
  return result
}

func sumAge(users []user, pips ...func([]user) interface{}) (result int) {
  var reusltSlice []int
  for _, f := range pips {
    runResult := f(users)
    switch runResult.(type) {
    case []user:
      users = runResult.([]user)
    case []int:
      reusltSlice = runResult.([]int)
    }
  }
  if len(reusltSlice) == 0 {
    log.Fatalln("パイプラインにmapAgeToSliceメソッドが含まれていません")
  }

  for _, age := range reusltSlice {
    result += age
  }
  return result
}

ここで、辞書型の代わりにユーザー構造体を導入してコードをより簡潔にし、可読性を向上させました。構造体型については、次の章である Go の型システムで詳しく説明します。

次に、Filter と Map 関数内のクロージャ関数を削除し、コード内で直接実装するように変更しました。これにより、コードを簡素化できます。また、パイプラインで Filter と Map 関数を一元的に宣言するために、それらの戻り値を空のインターフェース interface {} として宣言しました。

次に、重要なのは、Reduce 関数 sumAge の実装です。ここで、第 2 引数を可変長引数型として宣言し、複数の処理関数を渡すことができるようにしました。これらの処理関数は宣言された順序で順次呼び出されます。これらの処理関数の戻り値の型は空のインターフェースと宣言されているため、実行時にその戻り値の型を動的にチェックし、指定された変数に代入する必要があります。これにより、プログラムが期待するパスで実行され、型の問題によるエラーで終了することがありません。

パイプラインを使用することで、Filter->Map->Reduce のようなストリーム呼び出しをよりエレガントに実現することができます。さらに、パイプライン技術は HTTP リクエストのミドルウェア処理でも広く使用されています。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。