JavaScript 関数型プログラミング入門:最適化と純粋性の基礎

Vue.js の<transition-group>コンポーネントを利用する際、フレームワークによって自動生成される CSS クラスが、開発者が設計したカスタムアニメーションと衝突する可能性があります。これを防止するには、css="false"属性を設定し、デフォルトのクラス付与を抑制してください。また、ページ最初のレンダリング時にも要素が既に表示されているため、通常の遷移イベントがトリガーされないケースに対処するため、appearプロパティを用いて初次ロード時のトランジションを明示的に有効化する手法が推奨されます。

数値フォーマットの効率的な実装

伝統的なアプローチでは、桁ごとの区切り処理のためにループ処理が必要でしたが、現代の JavaScript では組み込みメソッドがこれを代替できます。例えば、英語圏の書式で 3 桁ごとにカンマを入力する場合、以下のコードが使用可能です。

value.toLocaleString('en-US')

さらに、漢字の数値表記への変換も同様に実装されています。

value.toLocaleString('zh-Hans-CN-u-nu-hanidec')

配列処理のパフォーマンス改善

配列に対する連鎖的な操作(フィルタリングやマッピング)を行う際、段階ごとに変換結果が新しい配列として作成されるとメモリーオーバーヘッドが発生します。この問題を解消する方法として、入力の各要素に対してすべての変換を順に適用し、一度のループで集約する方法があります。

const targetList = [10, 25, 55, 8, 14, 100]

const isOdd = (num) => num % 2 !== 0
const doubleVal = (num) => num * 2
const lessThanFifty = (num) => num < 50
const addThree = (num) => num + 3

// 従来のチェーン方式(中間データ発生あり)
const chainResult = targetList
  .filter(isOdd)
  .map(doubleVal)
  .filter(lessThanFifty)
  .map(addThree)

/*
[ 21, 25 ]
*/

一方、単一のパスで完結させるためのリデューサーアプローチでは、各要素に対してすべてのルールを適用します。

const composeMap = (fn) => (nextReducer) => (arr, item) => nextReducer(arr, fn(item))
const composeFilter = (predicate) => (nextReducer) => (arr, item) =>
  predicate(item) ? nextReducer(arr, item) : arr

const optimizedReducer =
  composeFilter(isOdd)(
    composeMap(doubleVal)(
      composeFilter(lessThanFifty)(
        composeMap(addThree)(
          (arr, v) => [...arr, v]
        )
      )
    )
  )

const singlePassResult = targetList.reduce(optimizedReducer, [])
/*
[ 21, 25 ]
*/

限定実行ユーティリティ (once)

関数の実行数を一回に限るためのパターンとして、クロージャを用いた実装があります。

const singletonExecute = (fn) => {
  let hasRun = false
  return (...params) => {
    if (!hasRun) {
      hasRun = true
      fn(...params)
    }
  }
}

返された関数内にはプライベートな状態変数が保持されており、外部からは変更不可です。非同期処理を含める場合、完了フラグのリセットタイミングなどに留意する必要があります。

const asyncTask = singletonExecute(async (val) => {
  await externalService(val)
})

asyncTask(10) // 実行開始
asyncTask(10) // 何もしない
singletonExecute(async (val) => {
  await externalService(val)
})(10) // 独立したクローンが生成されるため再び実行される

カーリング技術の基礎

関数型パラダイムにおける重要な概念の一つがカーリングです。これはハスケル・カリーに因んで命名されました。複数の引数を一つの引数で受け取り、次の引数を返す関数へと再帰的に展開する技術により、引数部分適用が可能になります。

const sumTriple = (a) => (b) => (c) => a + b + c

// 順次呼び出しによる引数バインド
const firstStep = sumTriple(1)    // 残りの引数用関数が返る
const secondStep = firstStep(2)   // さらに一つの引数用関数
const total = secondStep(3)       // 6 が得られる

// 同時呼び出しの場合、関数自体が返ってくるだけ
// sumTriple(1, 2, 3) は期待通りの計算結果を得られない

この構造はモジュール性が高く、再利用しやすい関数の構築を可能にします。

ポイントフリーと純粋性の確認

中間的な関数定義を省略するスタイルは「ポイントフリー」と呼ばれます。例えば、API からのレスポンス処理関数をそのまま通さずにチェーンに渡すことが可能です。

fetch('/data-url').then(handleData)

この形式は関数呼び出しのオーバーヘッドを減らすとともに、コード量も削減します。

純粋関数は数学的な関数と同様の特性を持ちます。具体的には以下の 2 つの条件を満たす必要があります。

  • 同一の入力に対し、常に同一の結果を返し、計算プロセスで変化がない。
  • 外部環境への影響(サイドエフェクト)を持たない。

このような性質を持つとき、式の値は評価順序に関係なく交換可能です。これを「参照透過性」と呼びます。単純な演算式はこの性質を持ちますが、ファイル I/O、現在時刻、乱数など実行時の状態に依存する式は、参照透過性を保持しません。

タグ: javascript vue-js functional-programming Performance currying

8月17日 04:53 投稿