ホームページ > ウェブフロントエンド > jsチュートリアル > JavaScript を再考する。部分的な適用、参照の透明性、遅延操作

JavaScript を再考する。部分的な適用、参照の透明性、遅延操作

Susan Sarandon
リリース: 2024-12-28 17:34:38
オリジナル
410 人が閲覧しました

Rethinking JavaScript. Partial Application, Referential Transparency, and Lazy Operations

皆さんこんにちは!少し前に、最新の TC39 提案を閲覧しているときに、私は興奮すると同時に少し懐疑的な提案を見つけました。 JavaScript の部分的なアプリケーション構文についてです。一見すると、これは多くの一般的なコーディングの問題に対する完璧な解決策のように思えますが、よく考えてみると、気に入った点と改善の余地がたくさんあることに気付きました。  

さらに良いことに、これらの懸念は、JavaScript をさらに強力にする可能性があるまったく新しいアイデアを引き起こしました。これらの機能が私たちの日々のコーディング方法をどのように変える可能性があるかを示す現実的な例を交えて、この旅にあなたを連れて行きましょう。

TLDR: 記事は私の古い問題から提案に基づいています: https://github.com/tc39/proposal-partial-application/issues/53


提案

部分アプリケーションでは、関数の一部の引数を「事前設定」して、後で使用できるように新しい関数を返すことができます。現在のコードは次のようになります:

const fetchWithAuth = (path: string) => fetch(
  { headers: { Authorization: "Bearer token" } },
  path,
);
fetchWithAuth("/users");
fetchWithAuth("/posts");
ログイン後にコピー
ログイン後にコピー

この提案では、次のような ~() 構文が導入されています。

const fetchWithAuth = fetch~({ headers: { Authorization: "Bearer token" } }, ?);
fetchWithAuth("/users");
fetchWithAuth("/posts");
ログイン後にコピー
ログイン後にコピー

何が起こっているかわかりますか? fetchWithAuth 関数は headers 引数を事前に入力するため、URL を指定するだけで済みます。 .bind() に似ていますが、より柔軟で読みやすくなっています。

この提案では、? の使用も許可されています。未入力の引数のプレースホルダーとして、および残りのパラメーターの ... として。例:

const sendEmail = send~(user.email, ?, ...);
sendEmail("Welcome!", "Hello and thanks for signing up!");
sendEmail("Reminder", "Don't forget to confirm your email.");
ログイン後にコピー
ログイン後にコピー

私の気に入っている点は、型の注釈を複製する必要がないことです!

便利そうですね?しかし、開梱すべきことはまだたくさんあります。


参照の透明性のケース

実際的な問題点から始めましょう: 関数のクロージャーと古い変数参照

何らかの通知をスケジュールしているとします。次のように書くとよいでしょう:

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout(() => alert(state.data), 1000)
  }
}
ログイン後にコピー
ログイン後にコピー

問題はもうわかりましたか? 「データ」プロパティはタイムアウト中に変更される可能性があり、アラートには何も表示されません。これを修正するには、値の参照を明示的に渡す必要があります。できれば、「setTimeout」で追加の引数を受け入れて、それをコールバックに渡します:

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout((data) => alert(data), 1000, state.data)
  }
}
ログイン後にコピー
ログイン後にコピー

悪くはありませんが、API 全体で広くサポートされていません。部分的に適用すると、このパターンをより普遍的なものにすることができます:

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout(alert~(state.data), 1000)
  }
}
ログイン後にコピー
ログイン後にコピー

関数作成時に state.data をロックすることで、古い参照による予期せぬバグを回避します。


繰り返しの計算を減らす

部分適用のもう 1 つの実用的な利点は、大規模なデータセットを処理する際の冗長な作業を排除できることです。

たとえば、反復ステップごとに追加データを計算する必要があるマッピング ロジックがあるとします。

const fetchWithAuth = (path: string) => fetch(
  { headers: { Authorization: "Bearer token" } },
  path,
);
fetchWithAuth("/users");
fetchWithAuth("/posts");
ログイン後にコピー
ログイン後にコピー

問題は this.some.another へのプロキシ アクセスにあり、各反復ステップを呼び出すとかなり負荷がかかります。このコードを次のようにリファクタリングする方が良いでしょう:

const fetchWithAuth = fetch~({ headers: { Authorization: "Bearer token" } }, ?);
fetchWithAuth("/users");
fetchWithAuth("/posts");
ログイン後にコピー
ログイン後にコピー

部分的な適用を使用すると、冗長性を減らすことができます:

const sendEmail = send~(user.email, ?, ...);
sendEmail("Welcome!", "Hello and thanks for signing up!");
sendEmail("Reminder", "Don't forget to confirm your email.");
ログイン後にコピー
ログイン後にコピー

共有計算を組み込むことで、パフォーマンスを犠牲にすることなく、コードがより簡潔になり、理解しやすくなります。


新しい構文を追加する理由

さて、ここからが頭を悩ませ始めました。提案された構文は洗練されていますが、JavaScript にはすでに多くの演算子があります。特に疑問符演算子 ? 。 ~() を追加すると、言語の学習と解析が難しくなる可能性があります。

新しい構文を導入せずに同じ機能を実現できたらどうなるでしょうか?


メソッドベースの代替案

tie メソッドを使用して Function.prototype を拡張することを想像してください:

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout(() => alert(state.data), 1000)
  }
}
ログイン後にコピー
ログイン後にコピー

これはもう少し冗長ですが、まったく新しい演算子の導入を避けています。プレースホルダーに追加の特殊記号を使用すると、疑問符を置き換えることができます。

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout((data) => alert(data), 1000, state.data)
  }
}
ログイン後にコピー
ログイン後にコピー

ビルド時の複雑さを追加することなく、完全にポリパイルされています!

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout(alert~(state.data), 1000)
  }
}
ログイン後にコピー
ログイン後にコピー

しかし、これは氷山の一角にすぎません。これにより、プレースホルダーの概念がさまざまな API 間で再利用可能になります。


遅延操作: さらに進化

ここからが本当に興味深いことになります。シンボルの概念を拡張して遅延操作を可能にしたらどうなるでしょうか?

例 1: .filter() と .map() の組み合わせ

電子商取引サイトの製品リストを処理していると仮定します。価格を四捨五入して割引商品のみを表示したいとします。通常は次のように書きます:

class Store  {
  data: { list: [], some: { another: 42 } }
  get computedList() {
    return this.list.map((el) => computeElement(el, this.some.another))
  }
  contructor() {
    makeAutoObservable(this)
  }
}
ログイン後にコピー

しかし、これには配列を 2 回繰り返す必要があります。遅延操作を使用すると、両方のステップを 1 つのパスに結合できます。

class Store  {
  data: { list: [], some: { another: 42 } }
  get computedList() {
    const { another } = this.some
    return this.list.map((el) => computeElement(el, another))
  }
  contructor() {
    makeAutoObservable(this)
  }
}
ログイン後にコピー

Symbol.skip は、最終的な配列から項目を除外するようにエンジンに指示し、操作を効率的かつ表現力豊かにします。

例 2: .reduce() での早期終了

最初の 5 つの販売からの合計収益を計算することを想像してください。通常、.reduce():
内で条件を使用します。

class Store  {
  data: { list: [], some: { another: 42 } }
  get computedList() {
    return this.list.map(computeElement~(?, this.some.another))
  }
  contructor() {
    makeAutoObservable(this)
  }
}
ログイン後にコピー

これは機能しますが、それでも配列内のすべての項目が処理されます。遅延リダクションを使用すると、早期終了を通知できます:

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout(alert.tie(state.data), 1000)
  }
}
ログイン後にコピー

Symbol.skip が存在すると、条件が満たされるとすぐに反復を停止するようにエンジンに指示でき、貴重なサイクルを節約できます。


なぜこれが重要なのか

これらのアイデア (部分的な適用、参照の透明性、遅延操作) は、単なる学術的な概念ではありません。彼らは現実世界の問題を解決します:

  • よりクリーンな API の使用法: 引数を事前にロックし、古い参照を回避します。
  • パフォーマンスの向上: 冗長な計算を排除し、より効率的な反復を可能にします。
  • 表現力の向上: 読みやすく保守しやすい、簡潔な宣言型コードを作成します。

~() に固執するか、tie や Symbol.skip などの代替案を検討するかにかかわらず、基礎となる原則には JavaScript の記述方法をレベルアップする大きな可能性があります。

ポリフィルが簡単でさまざまな用途があるため、私はシンボルのアプローチに投票します。


次は何ですか?

興味があるのですが、どう思いますか? ~() は正しい方向ですか? それともメソッドベースのアプローチを検討する必要がありますか?そして遅延操作はワークフローにどのような影響を与えるでしょうか?コメントで議論しましょう!

JavaScript の美しさは、コミュニティ主導の進化にあります。アイデアを共有し、議論することで、誰にとってもより適切に機能する言語を形作ることができます。会話を続けましょう!

以上がJavaScript を再考する。部分的な適用、参照の透明性、遅延操作の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート