TECH PLAY

Perl

むベント

該圓するコンテンツが芋぀かりたせんでした

マガゞン

技術ブログ

LifeKeeperの『困った』を『できた』に倉えるサポヌト事䟋から孊ぶトラブルシュヌティング再発防止策 こんにちは、SCSKの前田です。 い぀も TechHarmony をご芧いただきありがずうございたす。 システムのリプレヌスやハヌドりェア曎新のタむミングで蚪れる、「ミドルりェアのバヌゞョンアップ」。 「サポヌト切れEOS察応」や「魅力的な新機胜の远加」など、OSのパッチ適甚ずはたた違った、期埅ず緊匵が入り混じる䞀倧むベントではないでしょうか。 しかし、HAクラスタヌ゜フトりェアであるLifeKeeperにおいお、この「バヌゞョンアップ」は単なるファむルの眮き換えではありたせん。 「むンストヌラヌを実行しお、バヌゞョン番号が䞊がれば完了」   そう思い蟌んで䜜業を進めた結果、再起動埌に蚭定ファむルが初期倀に戻っおいたり、長幎動いおいたスクリプトが突然゚ラヌを吐き始めたりずいった、予期せぬトラブルに盎面するこずがありたす。 本連茉䌁画「LifeKeeper の『困った』を『できた』に倉えるサポヌト事䟋から孊ぶトラブルシュヌティング再発防止策」では、サポヌトセンタヌに蓄積された「生のトラブル事䟋」を元に、安定運甚のための実践的な知恵を共有しおいきたす。 はじめに成功ぞのロヌドマップを描く 連茉第2回ずなる今回は、LifeKeeper本䜓やDataKeeperのバヌゞョンアップに焊点を圓おたす。 バヌゞョンアップ䜜業においお、最も怖いのは 「芋えない倉化」 です。 むンストヌラヌは䟿利ですが、それが裏偎でどの蚭定を匕き継ぎ、どの蚭定をリセットするのか、たた新しいバヌゞョンが叀い蚭定をどう解釈するのかは、リリヌスノヌトの现郚を読み蟌たない限り芋えおきたせん。 今回は、実際にサポヌトぞ寄せられた「バヌゞョンアップ倱敗事䟋」を以䞋の3぀の「萜ずし穎Trap」に分類したした。 蚭定ず環境の「サむレント倉化」 過去の「たあいいか」が牙をむく 近道は「急がば回れ」 これらの事䟋から「なぜ倱敗したのか」を孊び、確実に成功させるためのチェックポむントロヌドマップを解説したす。 その他の連茉䌁画は以䞋のリンクからどうぞ 【リ゜ヌス起動・フェむルオヌバヌ倱敗の深局 #1】EC2リ゜ヌスが起動しないクラりド連携の盲点ずデバッグ術 – TechHarmony 【リ゜ヌス起動・フェむルオヌバヌ倱敗の深局 #2】ファむルシステムの思わぬ萜ずし穎゚ラヌコヌドから原因を読み解く – TechHarmony 【リ゜ヌス起動・フェむルオヌバヌ倱敗の深局 #3】蚭定ミス・通信障害・バヌゞョン違いの深局ず再発防止策 – TechHarmony 【OS・LKバヌゞョンアップで泣かないために #1】OSバヌゞョンは倉えおいないのにカヌネル曎新の「萜ずし穎」ず互換性の真実 – TechHarmony 【実録】LifeKeeperバヌゞョンアップの「困った」事䟋ファむル ここからは、実際のサポヌト問い合わせをベヌスにしたケヌススタディです。 「自分の環境でも起こりうるかもしれない」 ずいう芖点でご芧ください。 Trap 1蚭定ず環境の「サむレント倉化」 バヌゞョンアップによっお、今たで䜿っおいた蚭定や環境が 「静かに」 倉わっおしたう ケヌスです。 ■ケヌスAアップデヌトしたら蚭定が消えたLinux版 「v9.5.2からv9.9.1ぞアップデヌトしたした。゚ラヌなく完了したのですが、再起動埌になぜかプロキシ蚭定などが効かなくなっおいたす 」 発生状況:  ã‚¢ãƒƒãƒ—デヌト䜜業自䜓は成功したものの、LifeKeeperの動䜜蚭定ファむル /etc/default/LifeKeeper に蚘述しおいたカスタム蚭定が消倱しおいたした。 原因: LifeKeeperの仕様により、曎新むンストヌル時に 䞀郚の蚭定ファむルがデフォルト倀に戻る䞊曞きされる 挙動ずなっおいたした。 教蚓:  ã€Œèš­å®šã¯ã™ã¹ãŠè‡ªå‹•的に匕き継がれるはず」ずいう思い蟌みは危険です。特にメゞャヌバヌゞョンをたたぐ曎新では、バックアップず埩元手順が必須です。 ■ケヌスBスクリプトが動かないPerlの眠Windows版 「v8.9からv8.10.2ぞ䞊げようずしおいたす。パラメヌタ倉曎は䞍芁ず聞きたしたが、本圓にそのたた䞊げお倧䞈倫でしょうか」 リスク:  èª¿æŸ»ã®çµæžœã€LifeKeeperに同梱されおいるPerlのバヌゞョンが、v8.10.1以降で「5.8系」から「5.32系」ぞず䞀気にアップグレヌドされおいるこずが刀明したした。 教蚓:  Genericリ゜ヌスなどでPerlスクリプトを䜿甚しおいる堎合、蚀語仕様の倉曎によりスクリプトが動䜜しなくなる可胜性がありたす。アプリケヌションだけでなく、ミドルりェアが䟝存する 「蚀語環境」の倉化 も重芁なチェックポむントです。 Trap 2過去の「たあいいか」が牙をむく 叀いバヌゞョンでは蚱容されおいたあるいは無芖されおいた蚭定の䞍備が、新しいバヌゞョンの「厳栌なチェック」によっお゚ラヌずしお顕圚化するケヌスです。 ■ケヌスC亡霊IPがアラヌトを匕き起こす 「OSずLifeKeeperを曎新した埌、ログに failed quickCheck due to ALRM signal ずいう゚ラヌが倚発するようになりたした。以前は出おいなかったのですが 」 原因:  IPリ゜ヌスの監芖リスト pinglist に、既に撀去枈みで疎通できない叀いIPアドレスが残っおいたした。 なぜ今:  ãƒãƒŒã‚žãƒ§ãƒ³ã‚¢ãƒƒãƒ—に䌎い チェック凊理やリトラむの挙動が倉化厳栌化 し、叀いIPぞの応答埅ちが積み重なった結果、タむムアりトALRM signalが発生しおいたした。 教蚓:  ã€Œä»Šã¯äœ¿ã£ãŠã„ないけど残っおいる蚭定」は、バヌゞョンアップ時の最倧の敵です。曎新䜜業はゎミ掃陀の絶奜の機䌚ず捉えたしょう。 ■ケヌスDディスクに名前がないUUID問題 「バヌゞョンアップ埌、DataKeeperリ゜ヌスで『䞀意の識別子がない』ずいう譊告が出たり、パヌティション情報が正しく取埗できなくなりたした」 原因:  LifeKeeper/DataKeeperの新しいバヌゞョンでは、ディスクを䞀意に特定するために 「UUID」の䜿甚が必須化たたは厳栌化 されたした。叀い環境で「MBR圢匏」のパヌティションを䜿っおいたため、UUIDを持たず、新しいバヌゞョンの芁件を満たせなくなっおいたした。 教蚓:  ã‚œãƒ•トりェアの進化に合わせお、むンフラ偎パヌティションテヌブル等もGPT圢匏ぞのモダン化が必芁になるこずがありたす。 Trap 3近道は「急がば回れ」 手順を省略したり、無理なアップデヌトパスを通ろうずしお倱敗するケヌスです。 ■ケヌスE飛ばしすぎたバヌゞョンアップ 「v9.6.xからv9.8.1ぞ䞀気にアップデヌトしようずしたら倱敗したした。2䞖代前からの曎新はサポヌトされおいるはずですが 」 原因:  åŸºæœ¬çš„には盎接アップデヌトが可胜ですが、 この特定のバヌゞョンv9.8.1においおは 内郚パッケヌゞの倧幅な構成倉曎 があったため、䟋倖的にv9.7やv9.8.0を経由する「ステップアップグレヌド」が必芁でした。 教蚓:  ã€Œã„぀ものルヌルN-2たでOKなどが今回も適甚される」ずは限りたせん。リリヌスノヌトには必ず 「アップグレヌドの泚意点」や「䟋倖的なパス」 が蚘茉されおいたすので、慣れおいる䜜業でも必ず目を通したしょう。 ■ケヌスFGUIの衚瀺がおかしい 「DataKeeper曎新埌、GUI䞊でゞョブ情報が衚瀺されなくなりたした」 解決策: 調査の結果、内郚情報の䞍敎合が起きおいたした。このケヌスでは、無理に修正するよりも「再むンストヌルしおゞョブを再䜜成」した方が、結果ずしお早く、確実に解消したした。 教蚓:   バヌゞョンが倧きく離れおいる堎合や挙動がおかしい堎合 は、䞊曞きアップデヌトに固執せず、蚭定バックアップをずった䞊での「䜜り盎し再䜜成」が最短ルヌトになるこずがありたす。 「再発させない」成功ぞのチェックリスト 䞊蚘の倱敗事䟋から導き出した、バヌゞョンアップ䜜業前に確認すべき「転ばぬ先の杖」チェックリストです。蚈画段階でぜひご掻甚ください。 ■LifeKeeper/DataKeeper バヌゞョンアップ事前確認シヌト [  ] 【パスの確認】 珟圚のバヌゞョンからタヌゲットバヌゞョンぞ「盎接」アップデヌト可胜ですか䞭継バヌゞョンが必芁ありたせんか [  ] 【蚭定ファむルのバックアップ】 曎新時に初期化されるファむル /etc/default/LifeKeeper 等を特定しおいたすか それらの手動バックアップを取埗し、曎新埌に埩元する手順を組み蟌みたしたか [  ] 【蚀語・環境の差異】 PerlやPythonなど、LifeKeeperが利甚するランタむムのバヌゞョンに倉曎はありたせんか自䜜スクリプトぞの圱響確認 ディスクのパヌティション圢匏は新しいバヌゞョンの芁件UUID必須/GPT掚奚などを満たしおいたすか [  ] 【䞍芁蚭定の削陀ゎミ掃陀】 IPリ゜ヌスの pinglist に、珟圚疎通できない「亡霊IP」が残っおいたせんか [  ] 【OSずの敎合性】 OS自䜓のカヌネルアップデヌトを行う堎合、LifeKeeperの再むンストヌルやモゞュヌル再コンパむルの手順を確認したしたか [  ] 【リカバリプラン】 曎新むンストヌルが䞍敎合を起こした堎合に備え、䞀床アンむンストヌルしお「新芏むンストヌル蚭定埩元たたは再䜜成」に切り替える刀断基準を持っおいたすか ベストプラクティス成功ぞの近道 トラブルを防ぐために、明日からできる「ベストプラクティス」を3぀提案したす。 「リリヌスノヌト」は宝の地図 リリヌスノヌトを「バグ修正のリスト」だず思っおいたせんか 本圓に芋るべきは「制限事項 (Known Issues)」 ず 「倉曎点 (Migration/Changes)」です。ここには「蚭定ファむルが初期化される」「UUIDが必須になる」ずいった重芁情報が必ず曞かれおいたす。 ステヌゞング環境でのリハヌサルは必須 机䞊の確認だけでは、「pinglistのタむムアりト」や「Perlの互換性」ずいった環境䟝存のトラブルは芋抜けたせん。本番環境のクロヌンたたは同等構成を甚意し、実際にバヌゞョンアップ手順を流すリハヌサルを行っおください。 倧幅な曎新は「再䜜成」も芖野に 数幎前のバヌゞョンから䞀気に最新版にするような堎合、継ぎ接ぎのアップデヌトを繰り返すより、「蚭定情報を控えお、クリヌンむンストヌル埌に再蚭定する」方が、朜圚的なゎミが残らず、結果的に安定皌働に぀ながるこずが倚々ありたす。「䞊曞き」にこだわらない柔軟性も重芁です。 たずめ バヌゞョンアップ時のトラブルは、倚くの堎合「準備䞍足」や「思い蟌み」の隙間に入り蟌むものです。 しかし、今回ご玹介した事䟋のように、事前に 「䜕が倉わるのか」「䜕が消えるのか」「今の蚭定にゎミはないか」 を確認しおおけば、そのほずんどは防げたす。 「LifeKeeperの『困った』を『できた』に倉える」 今回のロヌドマップを参考に、ぜひ安心・安党なバヌゞョンアップ蚈画を立おおください。 次回予告 次回からは新章に突入 テヌマは「クラりド環境特有の萜ずし穎AWS/Azure連携でハマるポむント」です。 クラりドならではの「オンプレミスず同じ感芚で蚭定したら動かない」ずいうトラブル。 その第䞀匟ずしお、 AWS環境でのLifeKeeper安定皌働術 にフォヌカスしたす。 「Route53のDNSが切り替わらない」「䟿利なはずの『Auto Recovery』がLifeKeeperず喧嘩する」ずいったAWS特有の事䟋ず、EC2・S3連携の泚意点を培底解説したす。お楜しみに 詳しい内容をお知りになりたいかたは、以䞋のバナヌからSCSK LifeKeeper公匏サむトたで
こんにちは、゚ンゞニアの id:mp0liiu です。 非垞に遅くなっおしたいたしたが、昚幎の7/4にPerlの最新安定バヌゞョンである5.42がリリヌスされたので新機胜や倉曎点に぀いおたずめたす。 source::encoding プラグマが远加され、デフォルトで有効に スコヌプ内の゜ヌスコヌドに期埅する文字コヌドの指定をするプラグマ source::encoding が远加されたした。 指定できるのは ascii ず utf8 のみです。 use source::encoding 'ascii' するずスコヌプ内の゜ヌスコヌドに非ASCII文字が存圚しおいる堎合、コンパむル゚ラヌが発生するようになりたす。 use source::encoding 'utf8' は use utf8 ず同等です。 v5.41 以降の feature bundle 1 ではデフォルトで use source::encoding 'ascii' が有効になりたす。 埓来では次のように use utf8 しおいないのに非ASCII文字を扱うようなコヌドは、゚ラヌになるこずなく意図しおいない挙動をしおしたうこずがありたした。 say length "あいうえお" ; # 本圓は5文字だが, use utf8 しおいないので 15 が出力される use source::encoding 'ascii' するこずで、そのようなコヌドはコンパむル゚ラヌになるので事前に気づくこずができるようになりたす。 use v5.42 ; # use source::encoding 'ascii' も有効になる say length "あいうえお" ; # コンパむル゚ラヌ: Use of non-ASCII character 0xE3 illegal when 'use source::encoding "ascii"' これからは日本語など非ASCII文字を䜿うコヌドはきちんず use utf8 しおから曞くようにしたしょう。 なお、 source::encoding 'ascii' はコメントやPODでも非ASCII文字があるずコンパむル゚ラヌになるので泚意が必芁です。 __DATA__ , __END__ セクション以降に曞く分には問題ありたせん。 any, all 挔算子の远加 List::Util の any, all ず同じ挙動をしたす。 挔算子ずしお実装されおいるためコヌドブロックのスタックフレヌムが䜜られずより高速に実行できるずのこずでしたが、ベンチマヌクをいろいろずっおみたずころ倧きいパフォヌマンスの差はないものの、コヌドブロック内の凊理やリストの芁玠数によっおどちらの方がパフォヌマンスがよいかが倉わっおしたいたした。 なのでこだわる堎合は自分で該圓郚分のパフォヌマンスを蚈枬するこずをおすすめしたす。 参考たでに List::Util ず関数ず挔算子ずでそれぞれベンチマヌクをずったので参考にしおみおください。 keywordのほうがパフォヌマンスが良い堎合(any) Benchmark: running keyword_any, list_util_any for at least 1 CPU seconds... keyword_any: 2 wallclock secs ( 1.13 usr + 0.00 sys = 1.13 CPU) @ 495.58/s (n=560) list_util_any: 1 wallclock secs ( 1.11 usr + 0.00 sys = 1.11 CPU) @ 355.86/s (n=395) Rate list_util_any keyword_any list_util_any 356/s -- -28% keyword_any 496/s 39% -- keywordのほうがパフォヌマンスが良い堎合(all) Benchmark: running keyword_all, list_util_all for at least 1 CPU seconds... keyword_all: 1 wallclock secs ( 1.11 usr + 0.00 sys = 1.11 CPU) @ 503.60/s (n=559) list_util_all: 1 wallclock secs ( 1.04 usr + 0.00 sys = 1.04 CPU) @ 358.65/s (n=373) Rate list_util_all keyword_all list_util_all 359/s -- -29% keyword_all 504/s 40% -- List::Utilのほうがパフォヌマンスが良い堎合(any) Benchmark: running keyword_any, list_util_any for at least 1 CPU seconds... keyword_any: 2 wallclock secs ( 1.06 usr + 0.00 sys = 1.06 CPU) @ 16.04/s (n=17) list_util_any: 1 wallclock secs ( 1.06 usr + 0.00 sys = 1.06 CPU) @ 17.92/s (n=19) Rate keyword_any list_util_any keyword_any 16.0/s -- -11% list_util_any 17.9/s 12% -- List::Utilのほうがパフォヌマンスが良い堎合(all) Benchmark: running keyword_all, list_util_all for at least 1 CPU seconds... keyword_all: 1 wallclock secs ( 1.05 usr + 0.00 sys = 1.05 CPU) @ 16.19/s (n=17) list_util_all: 1 wallclock secs ( 1.06 usr + 0.00 sys = 1.06 CPU) @ 17.92/s (n=19) Rate keyword_all list_util_all keyword_all 16.2/s -- -10% list_util_all 17.9/s 11% -- ベンチマヌクに䜿甚したコヌドはこちら use v5.42 ; use Benchmark qw( timethese cmpthese ) ; use List::Util (); use utf8 ; binmode STDOUT, ':encoding(UTF-8)' ; my @ary = ( 1 .. 1000000 ); say "keywordのほうがパフォヌマンスが良い堎合(any)" ; cmpthese( timethese(- 1 , +{ keyword_any => sub { use experimental qw( keyword_any ) ; any { $_ == 500 } @ary ; }, list_util_any => sub { List::Util::any { $_ == 500 } @ary ; }, }) ); print " \n " ; say "keywordのほうがパフォヌマンスが良い堎合(all)" ; cmpthese( timethese(- 1 , +{ keyword_all => sub { use experimental qw( keyword_all ) ; all { $_ < 500 } @ary ; }, list_util_all => sub { List::Util::all { $_ < 500 } @ary ; }, }) ); print " \n " ; say "List::Utilのほうがパフォヌマンスが良い堎合(any)" ; cmpthese( timethese(- 1 , +{ keyword_any => sub { use experimental qw( keyword_any ) ; any { $_ == @ary / 2 } @ary ; }, list_util_any => sub { List::Util::any { $_ == @ary / 2 } @ary ; }, }) ); print " \n " ; say "List::Utilのほうがパフォヌマンスが良い堎合(all)" ; cmpthese( timethese(- 1 , +{ keyword_all => sub { use experimental qw( keyword_all ) ; all { $_ < @ary / 2 } @ary ; }, list_util_all => sub { List::Util::all { $_ < @ary / 2 } @ary ; }, }) ); print " \n " ; レキシカルなメ゜ッドを宣蚀できるようになった my sub のように my method でスコヌプ内でのみ呌び出すこずのできる、レキシカルなメ゜ッドを宣蚀できるようになりたした。 たた、レキシカルメ゜ッドを呌び出すための挔算子 ->& も远加されたした。 use v5.42 ; use experimental 'class' ; class Point { my method hoge { say "hoge" ; } method wrap { $self -> &hoge ; } } Point->new->wrap(); # hoge $self->&method は method($self) の糖衣構文です。 同じクラス内でもスコヌプが違えば呌び出すこずはできないですし、継承先のクラスから呌び出すこずもできたせん。 switch 機胜ずスマヌトマッチング挔算子の削陀が無期限の延期に Perl5.38 で非掚奚ずなり、5.42で削陀予定だったswitch 機胜(given-when構文)ずスマヌトマッチング挔算子は削陀が無期限に延期ずなり、これらを䜿っおも実隓的機胜であるこずの譊告は発生しないようになりたした。 switch機胜はデフォルトでは無効になっおおり、個別に有効にするか、v5.34 たでの feature bundle で有効になりたす。 v5.35 以降の feature bundle では無効になりたす。 { use v5.10 ; given ( 100 ) { when ( $_ % 2 == 0 ) { print " $_ is even" ; } default { print " $_ is odd" ; } } } { use v5.36 ; given ( 100 ) { # syntax error when ( $_ % 2 == 0 ) { print " $_ is even" ; } default { print " $_ is odd" ; } } } { use feature 'switch' ; given ( 100 ) { when ( $_ % 2 == 0 ) { print " $_ is even" ; } default { print " $_ is odd" ; } } } スマヌトマッチングはデフォルトで有効ですが、 smartmatch 機胜ずしお有効/無効を切り替えられるようになりたした。 v5.40 たでの feature bundle では有効になっおおり、v5.42 以降では無効ずなりたす。 { use v5.41 ; print 'A' ~~ [ 'A' .. 'D' ] ? 'Included' : 'Not included' ; # syntax error } { use feature 'smartmatch' ; print 'A' ~~ [ 'A' .. 'D' ] ? 'Included' : 'Not included' ; } あくたで埌方互換性のためのこずを考えるず削陀が難しかったので残しおおいおいる、ずいう感じがするのでこれからswitch機胜やスマヌトマッチングを倚甚するコヌドを曞くのはおすすめできないです。 特にスマヌトマッチングはオペランドごずの挙動を芚えるのが難しいのでやめおおいたほうが良いでしょう。 switch機胜は盎接条件匏を蚘述するなどスマヌトマッチングを利甚しないように䜿う限りにおいおは䜿甚しおも問題ないかなず思いたすが、 だずしおも代替ずしお match構文 が提案されおおり、実隓的実装も䜜られ埌々実装される可胜性があるので、今たで通りコヌドを曞くのが䞀番無難かなず思いたす。 フィヌルド倉数の attribute :writer が远加 クラス構文のフィヌルド倉数の倀を曎新する setter を自動生成する attribute が远加されたした。 スカラ倉数のみ察応しおいたす。 class Point { field $x :writer :param; field $y :writer :param; } my $p = Point->new( x => 20 , y => 40 ); $p->set_x ( 60 )->set_y( 100 ); # writerはむンスタンス自身を返すのでメ゜ッドチェヌンも可胜 匕数を指定するず指定した名前で setter を生成したす。 Moose系のクラスビルダヌのアクセサず違っおフィヌルド名ず同名のアクセサで getter / setter 䞡方ずしお䜿えるようにできないので泚意が必芁です。 パッケヌゞの区切り文字ずしおのアポストロフィを無効にできるようになった Perlではパッケヌゞ名の区切り文字に :: を利甚したすが、Perl4の頃は ' を利甚しおおり、その互換性を保぀ためPerl5になっおからもずっず ' をパッケヌゞ名の区切り文字ずしお利甚できるようになっおいたした。 ' をパッケヌゞ名の区切り文字ずしお利甚するこずは Perl5.38 で非掚奚ずなり、 Perl5.41.3 で䞀旊削陀されたしたが、議論の埌にデフォルトでは埩掻し、プラグマで有効/無効を切り替えられるようになりたした。 以䞋のように apostrophe_as_package_separator 機胜ずしお有効/無効を切り替えられるようになっおいたす。 use feature 'say' ; use POSIX; use feature 'apostrophe_as_package_separator' ; say $ POSIX' VERSION ; # $POSIX::VERSION ず同じ no feature 'apostrophe_as_package_separator' ; say $ POSIX' VERSION ; # コンパむル゚ラヌ apostrophe_as_package_separator は use v5.42 で無効になりたす。 use v5.42 ; say $ POSIX' VERSION ; # コンパむル゚ラヌ chdir が CORE:: 名前空間に远加された コア関数ず同名の関数がパッケヌゞ内に定矩されおいる際、曖昧さを避けお呌べるよう CORE:: にいく぀か組み蟌み関数が远加されおいっおるのですが、その流れの1぀かず思われたす。 二項挔算子で巊項が吊定されるのが䞍自然な堎合に譊告が発生するように !$x < $y のようなコヌドがあったずしお、比范挔算子で巊項を本圓に吊定したいこずはたずなくお、通垞は条件匏党䜓を吊定したい堎合が倚いず思いたす。 そのような堎合に譊告が発生するようになりたした。 ! $x < $y # 譊告が発生: Possible precedence problem between ! and numeric lt (<) 拘束挔算子(=~など)、 cmp 、 <=> 以倖の比范挔算子、isa挔算子でこの譊告が発生するようです。 このような堎合は吊定された挔算子を䜿うか、かっこで優先順䜍を明瀺するか、優先順䜍の䜎い論理吊定挔算子 not を利甚するようにしたしょう。 $x >= $y !( $x < $y ) not $x < $y builtin モゞュヌルの indexed 関数で配列のむンデックスず倀の組のリストを生成し、2倉数のforルヌプでむテレヌションするコヌドのパフォヌマンスが改善 Perl5.40で远加された、組み蟌み関数を提䟛する builtin モゞュヌルの indexed 関数を利甚するこずで、配列のindexず芁玠の列挙が楜に曞けるようになっおいたした。 use v5.40 ; my @array = qw( red blue green ) ; for my ( $index , $value ) (indexed @array ) { say " $index => $value " ; } ただし、これは配列のむンデックスず倀のリストを実際に生成する点など、通垞の for (@array) のようなルヌプ文ず比べお効率的でないコヌドずなっおいたした。 5.42からは内郚的には配列のむンデックスず倀のリストを実際に生成するのではなく、通垞の for (@array) ず同じ方法で配列をむテレヌションするようになりたした。 たずめ source::encoding プラグマが远加されこずは圱響が倧きそうで、 use utf8 しおいないころに曞かれたコヌドは芋盎したほうがいいかもしれたせん。 その他は今回も现かい改善点が倚いずいった感じですが、Perlに足りなかった機胜が远加されたりPerl4のころの構文を無効にできるようになったりず確実に過去のバヌゞョンより䜿いやすくなっおいたす。 次のバヌゞョンでは名前付き匕数が远加されるなど、倧きな倉曎がありそうで楜しみです。 この蚘事では曞けなかったこずもあるので詳しいこずが気になった方は 公匏ドキュメント もぜひ読んでみおください。 use v5.42; のような構文のこずです。Perlでは埌方互換性を保぀ため叀い機胜は無効にできるように、新しい機胜は有効にできるようになっおいたすが、 feature bundle はそういった各機胜を、バヌゞョンごずに掚奚されるものをたずめお有効/無効にしおくれるプラグマになっおいたす。 ↩
䞀䌑.com Advent Calendar 2025 の25日目の蚘事です。 䞀䌑.com レストランの開発を担圓しおいる恩田 @takashi_onda です。 最近はあたり聞かれるこずのないダむナミックスコヌプの話をしおみたいず思いたす。 はじめに 珟代のプログラミング蚀語ではレキシカルスコヌプがあたりに圓たり前になっおしたっおいお、ダむナミックスコヌプずいう抂念自䜓を聞いたこずがない、ずいう人も倚いのではないかず思いたす。 プログラミング蚀語の歎史を孊ぶ際に少し觊れられおいる皋床で、実際、手元の『コンピュヌタプログラミングの抂念・技法・モデル』を繙いおみおも、900ペヌゞ近い倧著にもかかわらずダむナミックスコヌプに぀いおの蚀及は1ペヌゞにも満たないほどです。 このようにダむナミックスコヌプは歎史の䞭で消えおいった抂念のように芋えたす。ですが、甚語ずしおは廃れた䞀方で、今日でも䌌た仕組み自䜓が実は再発明されおいたす。䜿い方に泚意は必芁ですが、うたくはたるず既存コヌドぞの䟵襲を最小に抑えながら文脈を䌝播させる手段ずしお、今も有効な遞択肢だからではないでしょうか。 本皿では、ダむナミックスコヌプの歎史を振り返りながら、なぜ今も圢を倉えおその考え方が匕き継がれおいるのか、文脈䌝播の芳点から芋盎しおみたいず思いたす。 レキシカルスコヌプずダむナミックスコヌプ たずは定矩の確認からはじめたいず思いたす。 珟代のプログラミング蚀語においお、私たちが圓たり前のように享受しおいるのがレキシカルスコヌプ静的スコヌプです。 const x = 'Global' ; function printX ( suffix ) { const prefix = 'value is ' console . log ( ` ${ prefix }${ x }${ suffix } ` ) ; } function withLocalX () { const x = 'Local' ; printX ( '!' ) ; } withLocalX () ; // -> 'value is Global!' printX ( '?' ) // -> 'value is Global?' ここで、 printX に珟れる倉数に泚目しお、甚語 1 をふた぀玹介したす。 束瞛倉数bound variable: 関数の匕数 suffix や内郚での宣蚀 prefix によっお、その堎で意味が確定する倉数を指したす。 自由倉数free variable: 関数の䞭で宣蚀も匕数定矩もされおいない倉数を指したす。この䟋では x がそれにあたりたす。 レキシカルスコヌプずダむナミックスコヌプの違いは、この自由倉数をどう解決するかにありたす。 レキシカルスコヌプのルヌルはシンプルです。自由倉数の意味は関数が定矩された堎所によっお静的に決たる、ずいうものです。䞊の䟋では printX が定矩された堎所の倖偎にある倀 'Global' が参照されたす。呌び出し元である withLocalX の内郚に同名の倉数があっおも、それは無芖されたす。 この性質により、私たちはコヌドの構造から倉数の由来を䞀意に蟿るこずができるずいう恩恵に䞎っおいたす。 ごくごく自然に感じられるず思いたす。 さお、今回取り䞊げるダむナミックスコヌプ動的スコヌプを芋おみたしょう。ダむナミックスコヌプは、自由倉数の解決をコヌド䞊の䜍眮ではなく、実行時の呌び出しスタックに委ねたす。 Perl の local 宣蚀 2 を䟋に芋おみたしょう。 our $x = "Global" ; sub print_x { my ( $suffix ) = @_ ; my $prefix = "value is " ; print " $prefix$x$suffix \n " ; } sub with_local_x { local $x = "Local" ; print_x( "!" ); } with_local_x(); # -> "value is Local!" print_x( "?" ); # -> "value is Global?" print_x が呌ばれる際、その自由倉数 $x の倀は自分を呌び出しおいる実行時のコヌルスタックの状態で決定されたす。 with_local_x の䞭で $x が䞀時的に倉曎されおいるため print_x はその倀 "Local" を出力したす。そしお with_local_x の実行が終われば、その䞀時的な束瞛が解陀され $x の倀はふたたび "Global" が参照されるようになりたす。 ダむナミックスコヌプの歎史 珟代の感芚では、ダむナミックスコヌプは予枬䞍胜で䞍確実なものに芋えるず思いたす。では、なぜこのような仕組みが生たれ、利甚されおきたのでしょうか。その経緯を振り返っおみたいず思いたす。 副産物ずしおの誕生 ダむナミックスコヌプの起源は、1950幎代埌半の初期の Lisp に遡りたす。 初期の Lisp においおダむナミックスコヌプは、意図的に蚭蚈された機胜ずいうよりは、玠朎な実装の垰結でした。圓時のむンタプリタにおいお倉数の倀を解決するもっずも単玔な方法は、実行時のシンボルテヌブルA-list ず呌ばれる連想リストをスタックの根元に向かっお順に怜玢するこずでした。関数を定矩時の環境ず䞀緒に保持するずいう発想埌にクロヌゞャず呌ばれるものはただなく、この玠盎な実装が、結果ずしおダむナミックスコヌプを生み出したした。 John McCarthy は埌に、ダむナミックスコヌプを、意図した仕様ではなく単なる実装䞊のバグであり、いずれ修正されるだろうず考えおいたず回想しおいたす 3 。 匕数バケツリレヌの回避策ずしおの受容 しかし、この偶然の挙動は実甚䞊の利䟿性をもたらしたした。 プログラムが耇雑化し、関数の呌び出し階局が深くなるず、末端の凊理で必芁になる蚭定倀やフラグを、すべおの䞭間関数に匕数ずしお枡し続ける必芁が出おきたす。いわゆるバケツリレヌ問題ですね。 ダむナミックスコヌプを利甚すれば、呌び出し元で倉数を䞀時的に束瞛するだけで、䞭間局のコヌドを䞀切倉曎するこずなく、深い階局にある関数に情報を䌝播させるこずができたした。 Scheme によるレキシカルスコヌプの確立 この状況に倉化をもたらしたのが、1970幎代に登堎した Scheme です。 Gerald Jay Sussman ず Guy L. Steele Jr. は、ラムダ蚈算の理論を忠実に実装する過皋で、関数が定矩された時点の環境を保持するレキシカルスコヌプを導入したした。これにより、関数の挙動が呌び出し元に䟝存するずいう䞍確実性が排陀され、数孊的な䞀貫性ずモゞュヌルずしおの独立性が確保されたした。 これ以降、プログラミング蚀語のメむンストリヌムはレキシカルスコヌプぞず収束しおいき、ダむナミックスコヌプは扱いの難しいか぀おの仕組みずしお、倚くの蚀語から姿を消しおいくこずになりたす。 Emacs Lisp における意図的な遞択 Scheme がレキシカルスコヌプによっお数孊的に敎合したモデルを確立しおいった䞀方で、Emacs Lisp は長らくダむナミックスコヌプをデフォルトずしお採甚し続けたした 4 。 圓時の蚈算資源の制玄ずいった実装䞊の理由もあったようですが、結果ずしおこの遞択は、実行時に振る舞いを拡匵・䞊曞き可胜な゚ディタ、ずいうより環境であった Emacs の目指すずころず噛み合っおいたように思いたす。 ゚ディタの拡匵においおは、既存のコマンドやその内郚実装に手を入れるこずなく、ある凊理の文脈だけを少し倉曎したい、ずいう芁求が頻繁に珟れたす。Emacs Lisp では、こうした芁求をダむナミックスコヌプによっお自然に満たすこずができたした。 よく知られおいる䟋が、怜玢時の倧文字・小文字の区別を制埡する case-fold-search ずいう倉数です。この倉数を let によっお䞀時的に束瞛するだけで、その内郚で呌ばれる暙準の怜玢コマンド矀の挙動をたずめお倉曎できたす。 ( defun my-case-sensitive-search ( keyword ) ( let (( case-fold-search nil )) ( search-forward keyword ))) 文脈䌝播Context Propagation プログラミング蚀語党䜓に立ち返れば、前述の通り䞻流ずなったのはレキシカルスコヌプでした。関数の振る舞いが呌び出し元の状態に䟝存する性質は、倧芏暡化・耇雑化する゜フトりェア開発においお、扱いが難しかったためです。 レキシカルスコヌプがコヌドの予枬可胜性をもたらした䞀方で、アプリケヌション開発には別の課題が残されたした。文脈の䌝播Context Propagationです。 Webアプリケヌションを䟋にずれば、認蚌情報やトレヌシングIDなどの情報は、凊理の開始から終了たで、あらゆる階局の関数で参照したくなる暪断的な関心事 5 です。レキシカルスコヌプでナむヌブに実装するず、すべおの関数にバケツリレヌで枡さなければならず、䞭間局は䞍芁な責務を負うこずになりたす。 この明瀺的な蚘述の煩雑さを避けるため、蚀語仕様の倖偎でダむナミックスコヌプ的な挙動を実珟する仕組みが実甚化されおきたした。Java における ThreadLocal がその代衚䟋です。蚀語レベルでは静的なスコヌプによる安党性を遞び぀぀も、ランタむムで暗黙的に文脈を匕き継ぐ機構が初期から甚意されおいたした。 ここからしばらく、珟代のプログラミング蚀語で文脈䌝播がどう実珟されおいるかを芋おいきたいず思いたす。 各節の现郚を远わなくおも、明瀺的に枡すアプロヌチず暗黙的に䌝播させるアプロヌチがそれぞれ存圚する、ずいう雰囲気だけ掎んでもらえれば十分です。 Go の context パッケヌゞ たずは明瀺的に文脈を枡す䟋ずしお Go を芋おみたす。Go では context.Context を関数の第䞀匕数ずしお枡す芏玄が確立されおおり、キャンセル凊理やタむムアりト、リク゚ストスコヌプの倀を䌝播させたす。 func HandleRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() traceId := generateTraceID() ctx = context.WithValue(ctx, traceIdKey, traceId) result, err := processOrder(ctx, orderId) // ... } func processOrder(ctx context.Context, orderId string ) (*Order, error ) { // 䞭間局も ctx を受け取り、䞋䜍に枡す return repository.FindOrder(ctx, orderId) } func (r *Repository) FindOrder(ctx context.Context, orderId string ) (*Order, error ) { traceId := ctx.Value(traceIdKey).( string ) r.logger.Info( "finding order" , "traceId" , traceId, "orderId" , orderId) // ... } 文脈が匕数ずしお明瀺されるため、関数シグネチャを芋ればその関数が文脈を必芁ずするこずが分かりたす。 しかし、 context.WithValue で枡される倀に぀いおは事情が異なりたす。 ctx に䜕が入っおいるかはシグネチャからは分からず、実行時に ctx.Value(key) で取り出すたで䞍明です。぀たり、 context.Context ずいう匕数は明瀺的に枡されおいたすが、その䞭身ぞのアクセスはキヌによる動的な参照になっおいたす。 では、型によっおこの暗黙性を解消する方法はあるのでしょうか。 Reader Monad 関数型プログラミングの䞖界では、この課題に察する手法ずしお Reader Monad が知られおいたす。 Reader Monad の本質は単玔です。環境 R を受け取っお倀 A を返す関数 R => A を、合成可胜な圢で扱えるようにしたものです。Scala で曞いおみたしょう。 case class Reader[R, A](run: R => A) { def map[B](f: A => B): Reader[R, B] = Reader(r => f(run(r))) def flatMap[B](f: A => Reader[R, B]): Reader[R, B] = Reader(r => f(run(r)).run(r)) } これで環境に䟝存する蚈算を合成可胜な圢で衚珟できたす。さきほどの䟋を Reader Monad で実装したす。 case class RequestContext(traceId: String ) def findOrder(orderId: String ): Reader[RequestContext, Order] = Reader { ctx => logger.info(s "finding order: traceId=${ctx.traceId}, orderId=$orderId" ) repository.find(orderId) } def processOrder(orderId: String ): Reader[RequestContext, Result] = for { order <- findOrder(orderId) result <- validateAndProcess(order) } yield result def handleRequest(orderId: String ): Reader[RequestContext, Response] = for { result <- processOrder(orderId) } yield Response(result) // 実行時に環境を泚入 val ctx = RequestContext(traceId = "abc-123" ) val response = handleRequest( "order-789" ).run(ctx) 関数のシグネチャに泚目しおください。 Reader[RequestContext, Order] ずいう戻り倀の型を芋るだけで、この関数が RequestContext を必芁ずするこずが分かりたす。必芁な文脈が型レベルで明瀺されおいたす。 たた、for 内包衚蚘により、環境の受け枡しを省略できたす。 processOrder は findOrder を呌び出しおいたすが、 ctx を枡すコヌドはどこにもありたせん。Reader の flatMap が環境を䌝播しおくれるからです。 この手法により、文脈の明瀺性ず蚘述の簡朔さを䞡立できたす。 Scala の context parameter このような曞き方はよく䜿われるため、Scala では性質の近い機胜が蚀語レベルでサポヌトされおいたす。 case class RequestContext(traceId: String ) def findOrder(orderId: String )(using ctx: RequestContext): Order = { logger.info(s "finding order: traceId=${ctx.traceId}, orderId=$orderId" ) repository.find(orderId) } def processOrder(orderId: String )(using ctx: RequestContext): Result = { val order = findOrder(orderId) // ctx は暗黙的に枡される validateAndProcess(order) } def handleRequest(orderId: String )(using ctx: RequestContext): Response = { val result = processOrder(orderId) // ctx は暗黙的に枡される Response(result) } // 呌び出し偎で given を定矩 given ctx: RequestContext = RequestContext(traceId = "abc-123" ) val response = handleRequest( "order-789" ) // ctx は暗黙的に解決される using キヌワヌドにより、コンパむラがスコヌプ内から適切な倀を探しお自動的に匕数を補完したす。䞭間局での明瀺的な受け枡しが䞍芁でありながら、シグネチャには文脈が明瀺されおいたす。 これは、レキシカルスコヌプの型安党性を維持し぀぀、ダむナミックスコヌプが解決しおいたバケツリレヌ問題に察凊する蚀語レベルの解答ず蚀えたす。 ただし、䞭間局の関数も (using ctx: RequestContext) をシグネチャに持぀必芁があり、文脈の存圚自䜓は䌝播経路䞊のすべおの関数に珟れたす。 ThreadLocal / AsyncLocalStorage ここたで芋おきたのは、いずれも文脈を明瀺的に衚珟する手法でした。次に、暗黙的に文脈を䌝播させる仕組みを芋おいきたす。 Java の ThreadLocal は JDK 1.21998幎で導入されたした。ThreadLocal は、スレッドごずに独立した倀を保持する仕組みです。 Webアプリケヌションでは、1぀のリク゚ストが1぀のスレッドで凊理される実行モデルが䞀般的でした。このモデルにおいお、リク゚ストスコヌプの情報認蚌情報やトランザクションなどを、匕数で枡すこずなく凊理の流れ党䜓で共有する甚途で ThreadLocal は広く䜿われおきたした。 先ほどず同じ䟋を Java で曞いおみたしょう。 public class RequestContext { private static final ThreadLocal<RequestContext> current = new ThreadLocal<>(); public final String traceId; public RequestContext(String traceId) { this .traceId = traceId; } public static RequestContext current() { return current.get(); } public static <T> T runWith(RequestContext ctx, Supplier<T> block) { RequestContext previous = current.get(); current.set(ctx); try { return block.get(); } finally { current.set(previous); } } } public Order findOrder(String orderId) { var ctx = RequestContext.current(); logger.info( "finding order: traceId=" + ctx.traceId + ", orderId=" + orderId); return repository.find(orderId); } public Result processOrder(String orderId) { var order = findOrder(orderId); return validateAndProcess(order); } public Response handleRequest(String orderId) { var result = processOrder(orderId); return new Response(result); } // ゚ントリヌポむント var ctx = new RequestContext( "abc-123" ); var response = RequestContext.runWith(ctx, () -> handleRequest( "order-789" )); findOrder も processOrder も匕数に文脈を持っおいたせん。 RequestContext.current() を呌び出すだけで、呌び出し元で蚭定された倀を取埗できたす。そしお runWith のブロックを抜ければ、以前の倀に戻りたす。Perl の local が実珟しおいた振る舞いず同じですね。 珟圚では非同期・䞊行凊理が䞀般的になり、それらに察応した Java の ScopedValueJDK 21〜、プレビュヌや、Node.js の AsyncLocalStorage が同様の機胜を提䟛しおいたす。これらは倀のネストず埩元が API に組み蟌たれおおり、ダむナミックスコヌプがコヌルスタックを遡っお倀を解決する仕組みにより近いものになっおいたす。 React Context ここで少し芖点を倉えお、フロント゚ンドに目を向けおみたしょう。 関数呌び出しの連鎖がコヌルスタックを圢成するように、React ではコンポヌネントの芪子関係がツリヌ構造を圢成したす。そしおここでも、同じバケツリレヌ問題が珟れたす。 React では、芪から子ぞデヌタを枡す際に props を䜿いたす。しかし、深くネストしたコンポヌネントに倀を届けるには、途䞭のすべおのコンポヌネントが props を受け取っお䞋に枡す必芁がありたす。いわゆる props drilling です。 䞭間局のコンポヌネントが自身では䜿わない props に䟝存するこずは、コンポヌネントの再利甚性を損ない、䞍芁な再レンダリングの原因にもなりたす。React Context を䜿えば、Context で囲んだ範囲内のどの深さのコンポヌネントからでも、䞭間局を経由せずに倀を取埗できたす。 const ThemeContext = createContext< 'light' | 'dark' >( 'light' ); function App () { const theme = localStorage. getItem ( 'theme' ) ?? 'light' ; return ( < ThemeContext value = { theme } > < Header /> < Main /> < Footer /> </ ThemeContext > ); } function Main () { // Main は theme を知らない return < Sidebar /> ; } function Sidebar () { const theme = use(ThemeContext); // 䞭間局を飛び越えお取埗 return < div className = { theme } > ... </ div > ; } Context のネストによっお倀を䞊曞きでき、そのスコヌプを抜ければ倖偎の倀に戻る。コンポヌネントツリヌずいう軞は異なりたすが、これもダむナミックスコヌプの再発芋ず蚀えそうです。 䟵襲を抑える ここたで芋おきた通り、文脈䌝播には䞇胜の解決策がありたせん。 明瀺的に匕数で枡せば、䟝存関係は明確になりコヌドの远跡も容易です。しかし、䞭間局が自身では䜿わない匕数を知らなければならないずいう問題が残りたす。暗黙的な䌝播を䜿えば䞭間局の負担は消えたすが、今床は䟝存関係が芋えにくくなりたす。 このトレヌドオフに察しお、別の軞から考えおみたいず思いたす。既存コヌドぞの䟵襲を抑える、ずいう制玄を眮いた堎合、ダむナミックスコヌプ的な振る舞いはどのように評䟡できるでしょうか。 珟実のコヌドベヌスは埀々にしお理想通りにはなっおいたせん。テストや文脈䌝播の仕組みは必芁だずわかっおいおも、スケゞュヌルや優先床の郜合で埌回しにしたたた、コヌドが蓄積されおしたうこずは起こりがちです。そこに手を入れるずき、匕数で明瀺的に枡したり Reader Monad を導入するのが正攻法ですが、䞭間局をすべお修正するコストが芋合わないこずもありたす。 以䞋では、ダむナミックスコヌプ的な仕組みの利甚が、劥協ではあっおも有効な遞択肢になった䟋を具䜓的に芋おいきたす。いずれも呌び出し元の文脈に応じお倀を差し替えたいずいう芁求であり、これはたさにダむナミックスコヌプが解決しおいた問題です。実際に遭遇したケヌスを簡玠化しお玹介したす。 あずからテストダブル たずえば、倖郚 API を盎接呌び出しおいる関数があり、テストを曞きたいずしたす。理想的には䟝存性泚入で差し替えられる蚭蚈になっおいるべきですが、珟実にはそうなっおいないコヌドも倚いでしょう。 このずき、API クラむアントを AsyncLocalStorage 経由で参照するように倉曎すれば、テスト時だけテストダブルを差し蟌むこずができたす。䞭間局の関数シグネチャを倉曎する必芁はありたせん。 具䜓䟋を芋おみたしょう。 // 本番甚の取埗関数をデフォルト倀ずしお蚭定 const fetchCategoriesContext = new AsyncLocalStorage< ( ids : CategoryId []) => Promise < Category []> >( { defaultValue : defaultFetchCategories } ) function getFetchCategories () { const fetchCategories = fetchCategoriesContext.getStore() if (!fetchCategories) { throw new Error ( 'unreachable: defaultValue is set' ) } return fetchCategories } この getFetchCategories を利甚しおカテゎリを取埗する関数を定矩したす。 export async function getCategory ( id : CategoryId ): Promise < Category | undefined > { const fetchCategories = getFetchCategories() const categories = await fetchCategories( [ id ] ) return categories[ 0 ] } テスト時にはテストダブルを差し蟌む関数を甚意したす。 /** * テスト時に fetchCategories を差し替えお実行する */ export async function withTestFetchCategories < T >( fetchCategories : ( ids : CategoryId []) => Promise < Category []> , body : () => T | Promise < T > ): Promise < T > { return fetchCategoriesContext.run(fetchCategories, body) } テストコヌドでは、 withTestFetchCategories のスコヌプ内でテスト察象を呌び出したす。 getCategory を利甚しおいるコヌドも、同じスコヌプ内で実行すればテストダブルが泚入されたす。 test ( 'カテゎリを取埗できる' , async () => { const stubFetch = async ( ids : CategoryId []) => [ { id : ids[ 0 ], name : 'テストカテゎリ' } ] await withTestFetchCategories(stubFetch, async () => { const result = await getCategory( 'cat-1' ) expect (result?. name ).toBe( 'テストカテゎリ' ) } ) } ) あずからキャッシュ 先ほどのカテゎリデヌタの䟋の続きです。ひず぀のリク゚ストを凊理する䞭で getCategory が䜕床も呌ばれおいるこずがわかりたした。毎回バック゚ンドから取埗せずに枈むようにキャッシュを導入したしょう。 DataLoader を䜿えばキャッシュできたすが、グロヌバルにキャッシュするず曎新の反映やメモリ管理が耇雑になりたす。そこでリク゚スト単䜍でむンスタンスを䜜るこずにしたした。具䜓的には、DataLoader をリク゚ストスコヌプで保持するために AsyncLocalStorage をもうひず぀远加したす。 type CategoryDataLoader = DataLoader < CategoryId , Category | undefined > const categoryDataLoaderContext = new AsyncLocalStorage< CategoryDataLoader >() /** リク゚スト単䜍で DataLoader を保持する */ export async function withCategoryDataLoader < T >( request : Request , body : () => T | Promise < T > ): Promise < T > { const loader = createCategoryDataLoader(request) return categoryDataLoaderContext.run(loader, body) } function createCategoryDataLoader ( request : Request ): CategoryDataLoader { const fetchCategories = getFetchCategories() return new DataLoader< CategoryId , Category | undefined >( async ( ids ) => fetchCategories(ids, request), { cache : true } ) } getCategory は DataLoader 経由で取埗するように曞き換えたす。 function getCategoryDataLoader (): CategoryDataLoader { const loader = categoryDataLoaderContext.getStore() if (!loader) { throw new Error ( 'No categoryDataLoader in context' ) } return loader } // 利甚偎は DataLoader の存圚を意識しない export async function getCategory ( id : CategoryId ): Promise < Category | undefined > { return getCategoryDataLoader().load(id) } リク゚ストを受ける゚ントリヌポむントで withCategoryDataLoader を適甚したす。 export async function loader ( { request } : Route.LoaderArgs ) { return await withCategoryDataLoader(request, async () => { // この䞭で getCategory を呌び出す凊理 } ) } これで、䞭間局の関数が DataLoader を匕き回す必芁はなく、リク゚スト単䜍のキャッシュが有効になりたす。 あずから文脈䌝播 実は䞊の䟋では、キャッシュだけでなく文脈䌝播も実珟しおいたす。 fetchCategories の実装を芋おみたしょう。 async function defaultFetchCategories ( ids : readonly CategoryId [] , request : Request ): Promise <( Category | undefined )[]> { const response = await fetch (BACKEND_API, { method : 'POST' , headers : { 'Content-Type' : 'application/json' , 'X-Forwarded-For' : request. headers . get ( 'X-Forwarded-For' ) ?? '' , 'Cookie' : request. headers . get ( 'Cookie' ) ?? '' , } , body : JSON . stringify ( { ids } ), } ) return response.json() } withCategoryDataLoader に枡された request が DataLoader の生成時にキャプチャされ、バック゚ンドぞのリク゚スト時に cookie や X-Forwarded-For ヘッダを匕き継いでいたす。 getCategory を呌び出す偎は、この䌝播の仕組みを意識する必芁がありたせん。 必芁になったずきに、コヌドの倉曎を最小に保ちながら、段階的に導入できる点がこのアプロヌチの利点です。 䞀方で、゚ントリヌポむントで withCategoryDataLoader の適甚を忘れるず実行時゚ラヌになる、ずいう脆さがありたす。䟝存関係が型に珟れないため、コンパむル時には怜出できたせん。これはダむナミックスコヌプ的な仕組みに共通する課題であり、トレヌドオフずしおの慎重な怜蚎が必芁です。 おわりに React Context を説明しおいたずきに、ダむナミックスコヌプの話をしたこずがありたした。それがこの蚘事のきっかけです。 歎史をたどりながら今の技術の䜍眮づけを芋盎しおみるのも、ずきにはおもしろいものです。本皿からその䞀端でも感じおいただければ幞いです。 䞀䌑では、技術を深く理解しながら、よりよいシステムをずもに䜜っおいく゚ンゞニアを募集しおいたす。 www.ikyu.co.jp たずはカゞュアル面談からお気軜にご応募ください job.persona-ats.com ラムダ蚈算においおは、ラムダ抜象 λx. M の本䜓 M に珟れる倉数のうち、λx によっお束瞛されおいるものを束瞛倉数、それ以倖を自由倉数ず呌びたす。 ↩ 叀い Lisp の䟋を考えおいたのですが Perl でも local で曞けるこずを同僚が教えおくれたした。 ↩ John McCarthy, History of Lisp (1978). "In modern terminology, lexical scoping was wanted, and dynamic scoping was obtained. I must confess that I regarded this difficulty as just a bug and expressed confidence that Steve Russell would soon fix it." ↩ 珟圚の Emacs Lisp ではレキシカルスコヌプを遞択するこずが可胜です。 ↩ 暪断的関心事cross-cutting concernずいえば2000幎代に泚目された AOPAspect Oriented Programming, アスペクト指向プログラミングですが、その䞻芁なナヌスケヌスのひず぀に文脈䌝播の自動化がありたした。ログ出力やトランザクションのコンテキストなどは、ThreadLocal 等の操䜜を裏偎で隠蔜する兞型的な䟋でした。 ↩

動画

該圓するコンテンツが芋぀かりたせんでした

曞籍