皆さんこんにちは!少し前に、最新の 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 間で再利用可能になります。
ここからが本当に興味深いことになります。シンボルの概念を拡張して遅延操作を可能にしたらどうなるでしょうか?
電子商取引サイトの製品リストを処理していると仮定します。価格を四捨五入して割引商品のみを表示したいとします。通常は次のように書きます:
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 は、最終的な配列から項目を除外するようにエンジンに指示し、操作を効率的かつ表現力豊かにします。
最初の 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 が存在すると、条件が満たされるとすぐに反復を停止するようにエンジンに指示でき、貴重なサイクルを節約できます。
これらのアイデア (部分的な適用、参照の透明性、遅延操作) は、単なる学術的な概念ではありません。彼らは現実世界の問題を解決します:
~() に固執するか、tie や Symbol.skip などの代替案を検討するかにかかわらず、基礎となる原則には JavaScript の記述方法をレベルアップする大きな可能性があります。
ポリフィルが簡単でさまざまな用途があるため、私はシンボルのアプローチに投票します。
興味があるのですが、どう思いますか? ~() は正しい方向ですか? それともメソッドベースのアプローチを検討する必要がありますか?そして遅延操作はワークフローにどのような影響を与えるでしょうか?コメントで議論しましょう!
JavaScript の美しさは、コミュニティ主導の進化にあります。アイデアを共有し、議論することで、誰にとってもより適切に機能する言語を形作ることができます。会話を続けましょう!
以上がJavaScript を再考する。部分的な適用、参照の透明性、遅延操作の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。