- TOP
- ã¿ã°äžèЧ
- Emacs
Emacs
ã€ãã³ã
該åœããã³ã³ãã³ããèŠã€ãããŸããã§ãã
ãã¬ãžã³
該åœããã³ã³ãã³ããèŠã€ãããŸããã§ãã
æè¡ããã°
äžäŒ.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 çã®æäœãè£åŽã§é èœããå
žåçãªäŸã§ããã ↩
ããã«ã¡ã¯ãRevCommã§ããã³ããšã³ããšã³ãžãã¢ããŠããnobkzãšç³ããŸãã ã¯ããã« æ®æ®µã¿ãªããã¯ãã©ã®ãããªãšãã£ã¿ãã䜿ãã§ããããïŒç§ã¯æ®æ®µããè²ããªãšãã£ã¿ã䜿ã£ãŠããŠããŸãè²ããªããã¹ããšãã£ã¿ã®å®è£
ãèŠãŠããŸããããã§ãä»åã¯ãããã¹ããšãã£ã¿ã«é¢ããŠéèŠãªãããã¹ããããã¡ã®å®è£
ã«ã€ããŠèŠãŠãããŸãããã ããã¹ããããã¡ãšã¯? ããããããã¹ããšãã£ã¿ãšã¯äœã§ããããïŒããã¯ãããã¹ãã®æ
å ±ãä¿æããŠããŠãŒã¶ãŒã®æç€ºã«ããå
容ã衚瀺線éããããã°ã©ã ã§ãã ããã¹ããããã¡ãšã¯ããããã¹ããšãã£ã¿ã®ããã¹ããã®ãã®ãä¿æããŠããå Žæã§ãããããŸããŸãªæ©èœãèŠæ±ãããŸãã ããã¹ããããã¡ã§ç¹ã«éèŠãªæ©èœãšããã°ãæååç
§ãæ¿å
¥ãåé€ã§ãã ã€ãŸãããã®charAtãinsertãeraseã®å¹çãéèŠã«ãªã£ãŠããŸãããã®ãããããã¹ããããã¡ã¯ãã®å¹çã®ããã«ããšãã£ã¿ããšã«å·¥å€«ããããŒã¿æ§é ããšã£ãŠããŸããä»åã¯ãã®ãããªããã¹ããããã¡ã®ããŒã¿æ§é ãèŠãŠãããŸãããã ããã¹ããããã¡ã®ããŒã¿æ§é ã€ã³ã¿ãŒãã§ãŒã¹ ä»åã®èšäºã§ã¯ãTypeScriptã§å®è£
ã玹ä»ããŠãããŸãããŸããç°¡åã«ããã¹ããããã¡ãæäœããããã®ã€ã³ã¿ãŒãã§ãŒã¹ãå®çŸ©ããŠã¿ãŸããããä»åã¯ãTypeScriptãå©çšããŸãã type charAt < Buffer > = ( buffer : Buffer , pos : number ) => string ; type insert < Buffer > = ( buffer : Buffer , pos : number , char : string ) => Buffer ; type erase < Buffer > = ( buffer : Buffer , pos : number ) => Buffer ; ç°¡åãªããŒã¿æ§é ã«ããããã¹ããããã¡ ããã¹ããããã¡ã®ããŒã¿æ§é ãšããŠãŸããç°¡åãªããŒã¿æ§é ã§ããé
åããªã¹ãã«ã€ããŠç޹ä»ããŠæ€èšããŠãããŸãããã é
åãBufferã«ãã ãŸããç°¡åã«ããã¹ããä¿æããããŒã¿æ§é ãšããã°ãæåååã§ããããæåååã¯ãèšèªã«ãããŸããåºæ¬çã«ã¯æåã®é
åãšãªã£ãŠããããšãå€ãã§ããããããã§é
åãšã¯ã æåã®é
åã§ãå®éã«TextBufferãå®è£
ããŠã¿ãŸããJSã«ã¯ãããŸããŸãªé
åã®ã¡ãœãããååšããŸããããããŠé
åã®ã¡ãœããã䜿ãããããããããã®ããé
åã®æ·»åã«ããã¢ã¯ã»ã¹ã代å
¥ã«ãã£ãŠå®è£
ããŠã¿ãŸãã ãŸãã¯ããŒã¿åã®å®çŸ©ããã§ããã type ArrayTextBuffer = { data : string [] , length : number ; capacity : number ; } ã²ãšãŸããstringã®é
åãšããŠãŸããã1æåã®é
åã ãšèããŠãã ãããããã§ãlengthã¯ããã¹ãã®é·ããcapacityã¯é
åã確ä¿ããŠããé·ãã§ããäžèšã®å³ã®äŸã§ããã°ãlengthã¯7, capacityã¯10ãšãªããŸãã æ¬¡ã«ãcharAtã®å®è£
ãããŠã¿ãŸããã const arrayCharAt: charAt < ArrayTextBuffer > = ( buffer , pos ) => buffer.data[pos]; æååç
§ã¯æ·»åã«ãã£ãŠçŽæ¥ã¢ã¯ã»ã¹ã§ãé
åã®ãµã€ãºã«äŸåããªãã®ã§ãèšç®é㯠ãšãªããŸãã æ¬¡ã«ãæ¿å
¥ãšåé€ãå®è£
ããŠã¿ãŸãããã以äžã®ããã«ãªããŸãã const arrayInsert: insert < ArrayTextBuffer > = ( buffer , pos , char ) => { if (buffer. length === buffer.capacity) { buffer.capacity *= 2 ; const newData = new Array (buffer.capacity); buffer.data. forEach (( c , i ) => { newData[i] = c } ); buffer.data = newData; } for ( let i = buffer. length - 1 ; i >= pos ; i--) { buffer.data[i+ 1 ] = buffer.data[i]; } buffer.data[pos] = char; buffer. length ++; return buffer; } const arrayErase: erase < ArrayTextBuffer > = ( buffer , pos ) => { for ( let i = pos ; i < buffer. length; i++) { buffer.data[i] = buffer.data[i+ 1 ]; } buffer. length --; return buffer; } ; ãã®ããã«ãæ¿å
¥ãããå ŽæããåŸã®æåã1æåããããŠãæåã代å
¥ããããåé€ã¯åçŽã«1æåéã«ããããŠãå®è£
ããŸãããã®ããã«ãµã€ãºã倧ãããªããšããããéãç·åœ¢ã«å¢ããŠãèšç®éã¯ã ãšãªããŸããæ¿å
¥ã«ãããŠã¯ãcapacityãšlengthãäžèŽãããšããããã¡ãæºæ¯ã«ãªã£ãŠããã®ã§ããããã¡ã2åã«ããŠããããšãããããšæããŸãã äžèšã«ããé
åãããã¹ããããã¡ã«ãããšã æååç
§ã¯ æ¿å
¥ãåé€ã¯ ãšãªããå°ããªããã¹ãã ãšãã®å®è£
ã§ååã§ããã倧ããªããã¹ãããŒã¿ã®æ¿å
¥åé€ã¯éå¹çã§ãããã ããæ«å°Ÿã«æ¿å
¥ãåé€ã¯ ã«ãªããŸããããããåŸã
Gab BufferããPiece Treeãšãã£ãããŒã¿æ§é ã«éèŠã«ãªã£ãŠããŸãã åæ¹åãªã¹ããããã¹ããããã¡ã«ããŠã¿ã åæ¹åãªã¹ããããã¹ããããã¡ã«ããŠã¿ãŸãããã ããŒã¿åã®å®çŸ©ã¯ä»¥äžã®éãã«ãªããŸãã type ListTextBuffer = { char : string , prev ?: ListTextBuffer , next ?: ListTextBuffer } | null ; // nullã§ç©ºãè¡šçŸ ãŸãæååç
§ã®å®è£
ãããŠã¿ãŸããããå®éã«ã¯æ·»ãåã®ç¯å²å€ã®ãšã©ãŒãªã©ãåºãã¹ãã§ãããä»åã¯ç°¡åãªå®è£
ã«çããŠããŸãã const getNode = ( buffer : ListTextBuffer , pos : number ) => { let node = buffer; for ( let i = 0 ; i < pos ; i++) { node = node?. next ?? null ; } return node; } const listCharAt : charAt < ListTextBuffer > = ( buffer : ListTextBuffer , pos : number ) => { return getNode(buffer, pos)?.char || "" ; } æååç
§ã¯ãå
é ããé çªã«åç
§ãåŸãªããšãããªããããé
åãšã¯å¯Ÿç
§çã«èšç®é㯠ãšãªããŸãã æ¬¡ã«æ¿å
¥ãšåé€ãå®è£
ããŸãããã const listInsert: insert < ListTextBuffer > = ( buffer : ListTextBuffer , pos : number , char : string ) => { const next = getNode(buffer, pos); const prev = next?.prev; const node : ListTextBuffer = { char , next , prev } ; if (next) { next.prev = node; } if (prev) { prev. next = node; } return buffer; } const listErase: erase < ListTextBuffer > = (buffer: ListTextBuffer, pos: number => { const targetNode = getNode(buffer, pos); const prev = targetNode?.prev; const next = targetNode?. next ; if (prev) { prev. next = next; } next.prev = prev; return buffer; } getNodeãçŸç¶ã®å®è£
ã ãšã ã§ãããäžæŠãããç¡èŠããŠãã ããããããšåã«ã€ãªããããã ããªã®ã§èšç®éã ãšãªããŸãã 屿åç
§æ§ ãšããã§ãããã¹ããšãã£ã¿ã«ã¯å±æåç
§æ§ããããŸãã屿åç
§æ§ãšã¯ãã¡ã¢ãªãªã©ã®ãªãœãŒã¹ã®ã¢ã¯ã»ã¹ã䜿çšããéšåã®åšèŸºãç¹°ãè¿ãã¢ã¯ã»ã¹ããããããšããæ§è³ªã§ããã«ãŒãœã«åšãã§ç·šéãããããããããããšèããã°ããããããã§ããããããã¹ããšãã£ã¿ã®å Žåã¯ãéåžž99%ã¯ã·ãŒã±ã³ã·ã£ã«ãªåç
§ *1 ãããã§ãããã®ãããªæ§è³ªãåæãšããŠããã¹ããšãã£ã¿ã¯ãããŒã¿æ§é ãæé©åããŠåŠçé床ã®åäžãå¯èœã«ãªããŸãã é
åãšãªã¹ãã®è©äŸ¡ ããŠãäžèšã® getNode() ã®å®è£
ã¯ç°¡åãªå®è£
ã«çããŠããŸããããå®éã«ããã®ã§ããã°ããã®ãã£ãã·ã¥ããŠãããªã©ããŠãåŠçé床ãå¹çåãããŠè¡ãã§ãããã ããã§ãé
åãšãªã¹ãã«ã€ããŠè©äŸ¡ãããŠãããšãé
åã¯ã·ã³ãã«ã§ããæ¿å
¥ãåé€ã ã§ããäžæ¹ã§ãªã¹ãã¯æååç
§ã ã§ããã©ã¡ããäžé·äžçããããŸãã屿åç
§æ§ãèãããšè¥å¹²ãªã¹ãã®æ¹ãè¯ãããã«æããŸãããä»ã«ããå®éã®ãšããã1æåã«ã€ããã€ã³ã¿ã2ã€å¿
èŠã«ãªã£ããããã®ã§ã¡ã¢ãªäœ¿çšéãå¢ããããªã©ã®åé¡ããããŸãã ãã®ãããªé
åããªã¹ãã®å®è£
ãããŒã¹ãšããŠä»ã®ãããã¡ããã®ãããªããã©ãŒãã³ã¹ãã©ã®ããã«æ¹åãããŠãããïŒã¿ãŠãããŸãããã è¡ããªã¹ãã§ç®¡çãã å
ã
ããé
åèªäœãæ¿å
¥åé€ã ãšãªãããšããããŸãããã®ããã«ãªãåå ã¯ãããããããã¹ãå
šäœäžã€ã®é
åã«è©°ã蟌ãã ããã§ãããããããšãããã¹ãå
šäœãäžã€ã®é
åã§ã¯ãªãåºåã£ãŠå°ããåäœã«åããŠããããšãªãã®ã¯èªç¶ãªçºæ³ãšãªããŸããSoftware Tools *2 ã§ã¯ãããã¹ããšãã£ã¿ã®ãããã¡ã®ããŒã¿æ§é ãšããŠãè¡åäœã§åããŠã管çããŠããŸããã ããŒã¿æ§é ã®å®çŸ©ã¯ä»¥äžã®éãã«ãªããŸãã type LineTextBuffer = { text : ArrayTextBuffer , prev ?: LineTextBuffer , next ?: LineTextBuffer } | null ; ããŠãæååç
§ãæ¿å
¥åé€ãå®è£
èªäœã¯çç¥ããŸãã(ãã²ãã£ãŠã¿ãŠãã ããïŒãæååç
§ã¯ãLãè¡æ°ãšããŠãæå㯠ãšãªããŸãããé£ç¶ã§åãè¡ãã¢ã¯ã»ã¹ããå Žåã¯åç
§ãããè¡ã確å®ããŠãããã ãšãªããŸãããŸãã察象è¡ã®æåæ°ãlãšçœ®ããšãæ¿å
¥åé€ã¯ãæå㯠ãåãè¡ãç·šéãããªã ãšãªããŸããé
åããªã¹ãã®ç®¡çãæ¯èŒãããšãããé«éã«ãªã£ãããšãããããšæããŸãã ãããã«è¡ããªã¹ãã§ç®¡çããã®ã¯ãããã¹ããšãã£ã¿ãå®è£
ãšããŠããã¿ãããŸããVSCodeã®æåã®å®è£
ã¯ãã®ãããªè¡åäœç®¡çã«ããå®è£
ã§ããããã ãçŸåšã§ã¯åŸè¿°ããPieceTreeã«ãã£ãŠå®è£
ãããŸã Gap Buffer è¡åäœã§ãªã¹ã管çããæ¹æ³ãããäžæ¹ã§ããã屿åç
§ã®æé©åãå³ã£ãããŒã¿æ§é ããããŸããããã¯ãGap Bufferã§ããGap Bufferã¯emacsã®ããã¹ãã®ããŒã¿æ§é ãšããŠå©çšãããŠããŸããGapãããã¡ã¯ãé
åã«Gapã€ãŸãééãããããŒã¿æ§é ã§ããäžã®å³ã®ããã¯ããŒã¿æ§é ã«ãªããŸãã ããŒã¿æ§é ãå®çŸ©ãããªã以äžã®ããã«ãªããŸãã type GapBuffer = { data : ArrayTextBuffer , capacity : number , gapStart : number , gapSize : number } ãŸãæååç
§ãã¿ãŠãããŸãããã const gapBufferChartAt : charAt < GapBuffer > = ( buffer , pos ) => { if (pos < buffer.gapStart) { return buffer.data[pos]; } else { return buffer.data[pos+buffer.gapSize]; } } æååç
§ã¯ãgapStartã®æåã§ããã°ããã®ãŸãŸposã§åç
§ããè¶
ããŠããã°gapSize+posã§åç
§ããŸããèšç®é㯠ãšãªããŸãã æ¬¡ã«æ¿å
¥ãåé€ãå®è£
ããŠã¿ãŸããããåäœã®ã€ã¡ãŒãžã¯ä»¥äžã®éãã§ãã å®è£
ã¯ä»¥äžã®ããã«ãªããŸãã const gapBufferInsert: insert < GapBuffer > = ( buffer , pos , char ) => { if (buffer.gapSize === 0 ) { buffer.gapStart = buffer.capacity; buffer.gapSize = buffer.capacity; buffer.capacity *= 2 ; const newData = new Array (buffer.capacity); buffer.data. forEach (( c , i ) => { newData[i] = c } ); buffer.data = newData; } while (pos !== buffer.gapStart) { const tmp = buffer.data[buffer.gapStart + buffer.gapSize - 1 ]; buffer.data[buffer.gapStart + buffer.gapSize - 1 ] = buffer.data[buffer.gapStart - 1 ]; buffer.data[buffer.gapStart - 1 ] = tmp; buffer.gapStart--; } buffer.data[pos] = char; buffer.gapSize--; buffer.gapStart++; return buffer; } const gapBufferErase: erase < GapBuffer > = ( buffer , pos ) => { while (pos !== buffer.gapStart) { const tmp = buffer.data[buffer.gapStart + buffer.gapSize - 1 ]; buffer.data[buffer.gapStart + buffer.gapSize - 1 ] = buffer.data[buffer.gapStart - 1 ]; buffer.data[buffer.gapStart - 1 ] = tmp; buffer.gapStart--; } buffer.gapSize++; buffer.gapStart--; return buffer; } ããŠãæ¿å
¥ã¯ãgapSizeã0ã§ããã°ããããã¡ãæºæ¯ãªã®ã§ãcapacityã2åã«ããŠããããšãããããŸããArrayTextBufferã®æãšäžç·ã§ããããããŠãã®ã£ããã®æåã®äœçœ®ãäžèŽãããŸã§ãã®ã£ãããããããŠããŸããåé€ã®å Žåããåé€ããäœçœ®ã«ããããŠããŸãããã ãåé€ããå Žåã¯ãé
åããããŒã¿ãæ¶ããã«ãgapã®äœçœ®ã ãããããŠãæååç
§ããèŠããªãããŠãããã§ãããåŸè¿°ããããŒã¿æ§é ããåé€ã¯å®ã¯ããŒã¿èªäœã¯ä¿æãããŠããŸãããèŠããªãããŠåé€ããŠããããŒã¿æ§é ã«ãªã£ãŠããããšãå€ãã§ããããŠãèšç®éã«ã€ããŠèããŠã¿ãŸããããããgapãäžçªåŸãã«ãã£ãŠãå
é ã«æåãæ¿å
¥ãããªãã°ãã»ãšããšé
åãšåæ§ã®æäœãšãªã ã«ãªããŸãããäžæ¹ã§ãé£ç¶ããŠããã¹ããç·šéãããªãã°ãã®ã£ããããããå¿
èŠããªãããã ã§ãã gap bufferã¯éåžžã«åªç§ãªããŒã¿æ§é ã§ãããã¯ããã¹ããšãã£ã¿ã®å±æåç
§æ§ãæå€§éã«æé©åãããã®ãšèšããã§ãããã PieceTable PieceTableã¯çŸåšã®VSCodeã§å©çšãããŠããPiece tree *3 ã®å
ãšãªã£ãããŒã¿æ§é ã§ããPieceãšã¯ãæçãšããæå³ã§ããPiece Tableãšã¯æçã®è¡šãšããæå³ã§ãããã¡ãã£ãšæå³ãããããªãã§ããããç°¡åã«èšãã°Piece Tableãšã¯ãããã¹ãã®æçãã€ãŸãåºéã衚ã«ããããŒã¿æ§é ã§ããå
ã»ã©ã®è¡åäœã®ç®¡çã®ã€ã¡ãŒãžãæã€äººããããšã¯æããŸãããäœãéãããšèšãã°ã ç·šéåºé ã衚ã«ãããšèããã°ãããããããããããŸããã ç°¡åã«ã€ã¡ãŒãžããŠã¿ãŸãããããŸã2ã€ã®é
åãããã¡ããããŸãããªãªãžãã«ãããã¡ãšã远èšçšã®ãããã¡ã§ãããªãªãžãã«ãããã¡ã¯èªã¿åãå°çšã§ãç·šéå
ã®ããŒã¿ãšãªããŸããèªã¿åãå°çšãªã®ã§ããã®ãããã¡èªäœã¯å€åããŸããããããŠã远èšçšã®ãããã¡ããæ«å°Ÿã«è¿œå ããã ãã®ããŒã¿ãããã¡ã§ãç·šéã§ã©ãã©ã远èšããŠå€§ãããªã£ãŠãããŸãããã®2ã€ã®ãããã¡ãšã¯å¥ã«ãããããã®ããã¹ãã®åºéã衚ã衚ããããŸãããã®åºéã®è¡šã¯ãé çªããããå®éã®ããã¹ãã®é ã«ãªãããã«äœãããŠããŸãã ã€ã¡ãŒãžãšããŠã¯ä»¥äžã®éãã§ãã ãã®ã€ã¡ãŒãžã ãšãåŸèŒ©ã¯ãLISPãããã§ããããšããæç« ã«ãªããŸãããªãªãžãã«ãããã¡ã®3çªç®ã®ãç«ãã¯è¡šã«ãªãã®ã§ãæç« ã«ã¯å
¥ã£ãŠãªãããšã«æ³šæããŠãã ããã ããŠããŒã¿åãå®çŸ©ããŠã¿ãŸãããã type PieceTable = { originalData : ArrayTextBuffer , additionalData : ArrayTextBuffer , table : Piece [] } // startãšendã¯éåºéã«ãã type Piece = { bufferType : "original" | "additional" , start : number , end : number } ããŠæååç
§ãå®è£
ããŠã¿ãŸãããã const pieceTableCharAt : charAt < PieceTable > = ( buffer , pos ) => { const { offset , piece } = localPiece(buffer, pos); // pieceãèŠã€ãããªãã£ããã²ãšãŸã空æåãè¿ããŠãã if (piece == null ) { return "" ; } return piece.bufferType == "original" ? arrayCharAt(buffer.originalData, piece.start + offset) : arrayCharAt(buffer.additinonalData, piece.start + offset); } // éåºéãªã®ã§é·ãã¯å·®åãåã£ãŠ+1 const pieceLen = ( p : Piece ) => p.end - p.start + 1 ; // äœçœ®ããããã®äœçœ®ãå«ãPieceãšoffsetãååŸãã const localPiece = ( buffer : PieceTable , pos : number , ): { piece : Piece | null ; pieceIndex : number ; offset : number } => { if (pos < 0 ) return { piece : null , pieceIndex : - 1 , offset : 0 } ; let rem = pos; for ( let i = 0 ; i < buffer.table. length; i++) { const piece = buffer.table[i]; const len = pieceLen(piece); if (rem < len) return { piece , pieceIndex : i, offset : rem } ; rem -= len; } //ã空ããŒãã«ã®æãªã© return { piece : null , pieceIndex : - 1 , offset : 0 } ; } ; Pieceã®åºéã®åŠçãããªããšãããªãåè€éã«ãªããŸããããããŠãèšç®éã¯Pieceã®æ°ã«äŸåããã®ã§ãPiecesã®æ°ãåçŽã«Pãšçœ®ã㊠ãšãªããŸãããã ãããã®å®è£
ã«ã¯å«ãã§ããŸããããéšåãé£ç¶ã§åç
§ããå ŽåãçºèŠããPieceãåæŽ»çšã§ãããã ã«ãªããŸãã æ¿å
¥åé€ãå®è£
ããŠã¿ãŸãããããŸãæ¿å
¥ã®ã€ã¡ãŒãžã§ãã以äžã«ãªããŸãã ã€ãŸããç·šéãããPieceãçºèŠããŠãåå²ããŠããã®éã«æ°ããPieceãæ¿å
¥ãããšããããšã§ããå®è£
ããŠã¿ãŸãããã const pieceTableInsert: insert < PieceTable > = ( buffer , pos , char ) => { // 远å ãããã¡æ«å°Ÿã«1æå远å const addPos = buffer.additionalData. length ; // 远å éå§äœçœ® arrayInsert(buffer.additionalData, addPos, char); const newPiece: Piece = { bufferType : "additional" , start : addPos, end : addPos } ; const { offset , piece , pieceIndex } = localPiece(buffer, pos); if (offset === 0 ) { // piece ã®å
é ã«æ¿å
¥ = ãã®åã« newPiece ãå
¥ãã buffer.table. splice (pieceIndex, 0 , newPiece); return buffer; } // pieceãèŠã€ãããªãã£ããã²ãšãŸãäœãããªãã§ãã if (piece == null ) { return buffer; } // piece ã split ããŠéã« newPiece ãæ¿å
¥ // piece: [start .. end] (closed) // left: [start .. start+offset-1] // right: [start+offset .. end] const left: Piece = { bufferType : piece.bufferType, start : piece.start, end : piece.start + offset - 1 , } ; const right: Piece = { bufferType : piece.bufferType, start : piece.start + offset, end : piece.end, } ; buffer.table. splice (pieceIndex, 1 , left, newPiece, right); return buffer; } ãã®ããã«æ¿å
¥ã®å Žåã¯ãaddtionalBufferã«è¿œå ããŠãPieceãåå²ããŠãæ°ããPieceãæ¿å
¥ããã°è¯ãããã§ããåé€ã®å Žåã¯å®è£
ããŸãããããã¡ããPieceãçºèŠããŠãåå²ããããŠãPieceã®startãendã®äœçœ®ãåã«ãããã°ããã®ã§ããããPieceãçºèŠãããŠãªãå Žåã¯ã ã§ãããPieceãçºèŠãããŠããŠãnewPieceã«é£ç¶ã§ç·šéããå Žåã¯ã远èšãããã¡ã®æ«å°Ÿã®è¿œå ãšãnewPieceã®endãå¢ããã°ããã ãã«ãªãã®ã§ ã«ãªããŸããåé€ãåæ§ã§ãã(åé€ã¯å®è£
ããªãã®ã§ãã£ã¬ã³ãžããŠã¿ãŠãã ãããïŒ äžèšã®å®è£
ã¯ç°¡åãªå®è£
ã§ãããæ¬æ¥ã§ããã°ãPieceãå䜵ãããããåŠçãããã®ã§ãããé·ããªãã®ã§å²æããŸãã Piece Tree ããŠVSCodeã§ã¯ãPieceã®ç®¡çããé£ç¶çãªè¡šã§ã¯ãªããèµ€é»æšã§æã€ããã«ããŸããããã®çç±ãšããŠãäžèšã®Piece Tableã®èª²é¡ç¹ãšããŠãç·šéãå¢ããã°å¢ããã»ã©ãPieceã®æ°ãå¢å€§ããæ¢çŽ¢ãããã«ããã¯ã«ãªã£ãŠããŸããããã§ãVSCodeã§ã¯ãé£ç¶ããTableãšããŠæã€ã®ã§ã¯ãªããèµ€é»æšãšããŠPieceãæã€ããã«ããŸããããããPiece Treeã§ãããã®ãããã§ãæååç
§ã®èšç®é㯠ãšãªããŸããã Piece Treeã®å®è£
ã®æ¹ã¯ãããŸãããïŒãã£ã¬ã³ãžããŠã¿ãŠãããã§ãããïŒ è¿œèšåã®å©ç¹ Piece TableãPiece Treeã¯ããã®ãããªæ§èœã®ã¿ãªããã远èšåã§ãããããã«ãç·šéå±¥æŽã®ç®¡çã«ã匷ããšèšãåŽé¢ããããŸãããªããªãããããã¡ã®è¿œèšã®ã¿ã§æ§æããããããå±¥æŽç®¡çã«ã¯åºæ¬çã«ã¯ãPieceã®å€æŽã ã远ãã°è¯ãããã§ãã Rope ããŠãæè¿ç§ã¯Zed *4 ãå©çšããŠããŸããZedã¯å
±åç·šéãAIãšãã£ã¿ãªã©ãæ³å®ããŠãè€æ°ã®ãŠãŒã¶ãŒããåç
§ããããšèšãèŠä»¶ãã§ãŠããŸãããã®ãããããã¹ããããã¡ã«Ropeãæ¡çšããŠããŸãã Ropeã¯Piece Treeãšåæ§ã«å€§ããªæååãå°ããªæçã«åãããããæšæ§é ã§ä¿æããããŒã¿æ§é ã§ããRopeã¯æ«ç«¯ã®èã¯ãæåã®æçã衚ããŸãããè以å€ã®ããŒãã¯å·Šéšåæšã®æåæ°ã瀺ããŠããŸããã€ã¡ãŒãžã§èšããšä»¥äžã®éãã§ãã ããŒã¿æ§é ãšããŠã¯ä»¥äžã®ããã«ãªããŸããä»åãèã®èšèšãPiece Treeãšåæ§ã®èšèšã«ããŠããŸããïŒäžè¬çã«ã¯ãstringãšããŠæã€ããšãå€ãã§ããïŒ type RopeNode = { kind : "node" ; weight : number ; left : RopeNode | RopeLeaf ; right : RopeNode | RopeLeaf ; } ; type RopeLeaf = { kind : "leaf" ; bufferType : "original" | "additional" ; start : number ; // closed end : number ; // closed } ; type RopeBuffer = { original : ArrayTextBuffer ; additional : ArrayTextBuffer ; root : ( RopeNode | RopeLeaf ) | null ; } ; æååç
§ã«ã€ããŠç°¡åã«å®è£
ããŠã¿ãŸããããåäœã€ã¡ãŒãžã¯ä»¥äžã®éãã«ãªããŸãã ã€ãŸããããŒããšæ¯èŒããŠãå°ãããªãå·Šã倧ãããªããå³ã«é²ã¿ãå³ã«é²ããšãã«ã調ã¹ãæåæ°ãåŒãã°ããã®ã§ããã // åã¬ãŒã const isLeaf = ( x : RopeNode | RopeLeaf ): x is RopeLeaf => x.kind === "leaf" ; const leafLen = ( leaf : RopeLeaf ) => leaf.end - leaf.start + 1 ; const ropeCharAt: charAt < RopeBuffer > = ( buf , pos ) => { if (buf.root == null ) return "" ; if (pos < 0 ) return "" ; let node: RopeNode | RopeLeaf = buf.root; let i = pos; while ( true ) { if (isLeaf(node)) { if (i >= leafLen(node)) return "" ; const idx = node.start + i; return node.bufferType === "original" ? arrayCharAt(buf.original, idx) : arrayCharAt(buf.additional, idx); } // node 㯠RopeNode ã« ì¢ãŸã£ãŠã if (i < node.weight) { node = node.left; } else { i -= node.weight; node = node.right; } } } ; æšã¯ãã©ã³ã¹ããŠããåæãªãã°ãæååç
§ã¯é«ãã«æ¯äŸã㊠ïŒL 㯠leaf æ°ïŒã«ãªããŸãã æ¬¡ã«æ¿å
¥ãåé€ãèŠãŠãããŸããããRopeã®æšã®æ¿å
¥ãšåé€ã¯ãåºæ¬ã¯ splitïŒåå²ïŒ ãš concatïŒé£çµïŒ ã®2ã€ã«èœãšã蟌ããŸãã split(tree, pos) : pos ã§æšãå·Šå³ã® Rope ã«åå²ãã concat(left, right) : 2ã€ã® Rope ãé£çµããïŒæ°ããããŒããäœãïŒ ãããã§ãããšãæ¿å
¥ã¯ (L, R) = split(tree, pos) 远å ãããã¡æ«å°Ÿã« char ã远å ãããããæã leaf ãäœã tree' = concat(concat(L, newLeaf), R) åé€ã¯ (A, B) = split(tree, pos) (trash, C) = split(B, 1) ïŒå
é 1 æåãèœãšãïŒ tree' = concat(A, C) ã§è¡šçŸã§ããŸãããªã®ã§ãä»åã¯concatãšsplitã®ã¿å®è£
ããŠã¿ãŸãã const len = (t: (RopeNode | RopeLeaf) | null ): number => { if (t == null ) return 0 ; if (isLeaf(t)) return leafLen(t); return t.weight + len(t.right); } ; const concat = ( a: (RopeNode | RopeLeaf) | null , b: (RopeNode | RopeLeaf) | null , ): (RopeNode | RopeLeaf) | null => { if (a == null ) return b; if (b == null ) return a; return { kind : "node" , weight : len(a), left : a, right : b } ; } ; // split(t, pos) => [0..pos-1], [pos..end] const split = ( t: (RopeNode | RopeLeaf) | null , pos: number, ): [ (RopeNode | RopeLeaf) | null , (RopeNode | RopeLeaf) | null ] => { if (t == null ) return [ null , null ] ; if (pos <= 0 ) return [ null , t ] ; const tlen = len(t); if (pos >= tlen) return [ t, null ] ; if (isLeaf(t)) { const leftLen = pos; const leftLeaf: RopeLeaf = { kind : "leaf" , bufferType : t.bufferType, start : t.start, end : t.start + leftLen - 1 , } ; const rightLeaf: RopeLeaf = { kind : "leaf" , bufferType : t.bufferType, start : t.start + leftLen, end : t.end, } ; return [ leftLeaf, rightLeaf ] ; } if (pos < t.weight) { const [ l , r ] = split(t.left, pos); return [ l, concat(r, t.right) ] ; } else { const [ l , r ] = split(t.right, pos - t.weight); return [ concat(t.left, l), r ] ; } } ; ããã§æ¿å
¥ãåé€ã¯ä»¥äžã®ããã«å®è£
ã§ããŸãã const ropeInsert: insert < RopeBuffer > = ( buf , pos , char ) => { // 远å ãããã¡æ«å°Ÿã« 1 æå远å const addPos = buf.additional. length ; arrayInsert(buf.additional, addPos, char); const newLeaf: RopeLeaf = { ãã... ç¥ } ; const [ L , R ] = split(buf.tree, pos); return { tree : concat(concat(L, newLeaf), R) } ; } ; const ropeErase: erase < RopeBuffer > = ( buf , pos ) => { const [ A , B ] = split(buf.tree, pos); const [ , C ] = split(B, 1 ); // å
é 1 æåãæšãŠã return { tree : concat(A,C) } ; } ; 泚æç¹ãšããŠãä»åã¯äºåæšã®å¹³è¡¡ãèæ
®ããŠããªãå®è£
ã§ããå®éã«ã¯ãããªãã«è€éã«ãªããŸãã æšããã©ã³ã¹ããŠããåæãªãããã¡ãã®èšç®éãã ãšãªããŸããä»åã®å®è£
ã«ã¯å«ãŸããŠããŸããããåãLeafã«é£ç¶ã§ç·šéããéãå®è£
ã«ãããŸããèšç®éã¯æ«ç«¯ã®èã®ãµã€ãºãlãšçœ®ã㊠ãšãªããŸãã Ropeãšå
±åç·šéã®å®¹ææ§ ããŠãRopeã¯ãPieceTreeãGap Bufferãšæ¯èŒãããšè¥å¹²æ§èœãæªãããã«èŠããŸããããããªãããRopeã¯æ§èœãããå
±åç·šéã«äž»çŒã眮ããŠããŸããäžèšã®ããŒã¿æ§é ã¯ãæ¿å
¥ãåé€ãããã¿ãŠã¿ããšãå
ã®æšã®ããŒã¿æ§é ãçŽæ¥ç·šéããŠããŸãããconcatã¯åã«å
ããæšã®çµåã§ãããsplitãåã«åå²ããã ãã§ãçŽæ¥æšã®ã«å€æŽãå ããŠããŸããããããŠãåé€ãæ¿å
¥ã¯ãã®concatãsplitãçµã¿åãããŠå
ã«ããæšãããæ°ããæšãæ§ç¯ããŠããã ããªã®ã§ãã æ¿å
¥ãšåé€ã®ã€ã¡ãŒãžã¯ä»¥äžã«ãªããŸãã ã€ãŸããRopeã¯æŽæ°ã®ãã³ã«å
šäœãã³ããŒããã倿Žãå¿
èŠãªéšåã ããæ°ããäœããæ®ãã¯å
±æããããŒã¿æ§é ã«ãªããŸããã€ãŸããå
ã®ããã¹ããä¿æãããŸãŸã倿ŽåŸã®ããã¹ããæ§ç¯ããããšãã§ãããŸãã䞊è¡ç·šéãªã©ãèãããšã䞊è¡ããŠè€æ°ã®ããŒãžã§ã³ãæã¡ãããããã®çµæãå
±åç·šéãªã©ã®æ©èœã«åŒ·ãããŒã¿æ§é ã«ãªã£ãŠããŸãã ããã«ãPiece Tableãšåæ§ã«å±¥æŽæäœã«ã匷ãããŒã¿æ§é ã«ãªã£ãŠããããšããããããšæããŸãã ãŸãšã æ¬èšäºã§ã¯ãããã¹ããšãã£ã¿ã®å
éšã§äœ¿ãããããã¹ããããã¡ã®ããŒã¿æ§é ããæååç
§ïŒcharAtïŒã»æ¿å
¥ïŒinsertïŒã»åé€ïŒeraseïŒãšãã芳ç¹ã§èŠãŠããŸããã é
å: charAt ã¯åžžã« O(1)ããã ã insert/erase ã¯åŸãããããã®ã§ O(N)ã éãåç
§ã®ä»£åãšããŠç·šéãéãã ãªã¹ã: insert/erase ã¯ç¹ãæ¿ãèªäœã¯ O(1) ã ããäœçœ®ãŸã§è¡ãæ¢çŽ¢ã O(N)ã ç·šéã¯è»œãâã§ããâããããããç®çå°ã«èŸ¿ãçãã®ãé
ãã è¡åäœç®¡ç: ãå
šæåã1æ¬ã«æã€ãããã§ O(N) ã«ãªããªããåå²ããŠå±æåããã â åç
§ã»ç·šéã âè¡æ° + è¡å
â ã«èœã¡ãçŸå®ã®ç·šéïŒåãè¡ãè§Šãç¶ããïŒã§å¹ãã Gap Buffer: 屿åç
§æ§ïŒã«ãŒãœã«è¿åãé£ç¶ç·šéïŒãåæã«ãâããã ãO(1)â ãäœããã®ã£ããç§»åã¯ææª O(N) ã ããé£ç¶ç·šéã¯å®è³ª O(1)ã Piece Table / Piece Tree: æå忬äœãåããããç·šéãâåç
§ã®æçïŒPieceïŒâãšããŠè¡šçŸãããPiece ãå¢ãããšæ¢çŽ¢ãå¹ãã®ã§ã衚ïŒé
åïŒâ æšïŒèµ€é»æšïŒã§ O(P) â O(log P) ã«ããïŒVSCodeïŒã Rope: æçïŒæšã§ãsplit/concat ã«ç·šéãéå
ããã倿Žã¯ãå¿
èŠãªçµè·¯ã ãäœãçŽããæ®ãã¯å
±æãã«ãªããå±¥æŽã»è€æ°ãã¥ãŒã»äžŠè¡æäœã«èªç¶ã«åŒ·ãã ããŸããŸãªããã¹ããšãã£ã¿ã®ããã¹ããããã¡ã®ããŒã¿æ§é ã«ã€ããŠã¿ãŠãããŸãããæ®æ®µã䜿ã£ãŠãããšãã£ã¿ãã©ã®ãããªå®è£
ã«ãªã£ãŠãããããŸãæ°ã«ããããšãªãæ¹ãããããããã£ããããããããŸãããããã®ããã«ããŸããŸãªããŒã¿æ§é ã®å·¥å€«ããããšãŠãé¢çœããã®ãšãªã£ãŠããŸãã ãŸãããããŸã§æžããŠããŸããããããã¹ããšãã£ã¿ã®å®è£
ã®è©±é¡ãŸãŸã ãŸã ãããããããããšãã°ãæ¹è¡ã®ç®¡çããã«ãŒãœã«ã®ç§»åããç¯å²éžæãã¹ã¯ããŒã«ãæãè¿ããæ€çŽ¢ãå
±åç·šéãªã©ããŸããŸãªããšã«ã€ããŠãŸã ãŸã æ·±æãã§ãããšããããããŸããæ¯éãšãæ®æ®µã䜿ãã®ãšãã£ã¿ã«ã€ããŠæ¢çŽ¢ããŠã¯ãããã§ããããïŒ *1 : https://www.cs.unm.edu/~crowley/papers/sds.pdf *2 : P.J. Kernighan, Brian W. Plauger Software Tools 1976 Addison-Wesley Professional 020103669X *3 : https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation *4 : https://zed.dev/
ããã¯æ ªåŒäŒç€ŸLabBase ããã¯ã«ã¬ã³ã㌠Advent Calendar 2025 12æ¥ç®ã®èšäºã§ãã æŠèŠ åŠçæä»£ãããè«æãªã©ã®è°è«ã®ç²Ÿèªã«å¯ŸããŠãã絶察誰ããããè«ççãªæŽåæ§ã確ãããããšãããã ãããªãããšæãããã®åºŠã«è«ççãªæŽåæ§ã®å確èªã«å¯ŸããŠããã©ããããæããŠããŸããã æ¬çš¿ã§ã¯åœ¢åŒæå³è«ãšå®çèšŒææ¯æŽç³»ãå©çšããŠãããããè°è«ã圢åŒåããããšã§ããããæç Žããããšãç®è«ã¿ãŸãã ããèªç¶èšèªããã³ã³ãã€ã«ãã§ãããïŒ ããã°ã©ãã³ã°èšèªã§ã¯ãã³ãŒããæžããšåæã«ã³ã³ãã€ã©ãéçè§£æããŒã«ãåããææ³ãã¹ãåã®äžæŽåãè«ççãªç Žç¶»ãæ€åºããŠãããŸãã
åç»
該åœããã³ã³ãã³ããèŠã€ãããŸããã§ãã
æžç±
該åœããã³ã³ãã³ããèŠã€ãããŸããã§ãã









