Don't Repeat Yourself

Don't Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of all kinds. -- wikipedia

logback で環境ごとにロギング設定を変えたい場合にやること

ググると Spring も込みの設定方法しか出てこなかったので,メモしておきます.言語は Scala,ビルドツールは sbt を使用しています.logback-classic を使用しているようです.

やろうとしていること

  • logback.xml 内で,開発環境/ステージング環境/本番環境それぞれでログの仕方を切り替えたい場合
  • かつ,環境情報は System.setProperty(key, value) あるいは環境変数で設定されている場合

ハマったポイント

  • ドキュメントどおりに if 文を書いてみる.
  • 環境変数システムプロパティを正しく設定しているのに,なぜか <if condition='p("key").contains("value")'> がうまく効いていない.
  • それゆえ,if 文の内容がいつまで経っても反映されない.

原因

  • ログ上に出ている警告を注意深く読むと,「janino」というライブラリがクラスパスにないと言われる (知らんがな).

解決策

  • janino というライブラリを Maven や sbt で依存関係に加えておく.

一応,このページ の if 文の箇所に,

Note that conditional processing requires the Janino library.

とは書いてありますね.Janino Library の設定方法まで丁寧に書いてあります

お役に立ちますと幸いでございます.ちなみにいろいろ試して,30分くらい溶かしました.エラーメッセージは丹念に読みましょう.

ちなみに

この構文で取得できる p("key") の参照先は,システムプロパティでもよいですし,環境変数でもよいです.

システムプロパティであれば,java jar xxx.jar -DENV=dev などと書けば (あるいは System#setProperty を用いる手もあります), p("ENV").contains("dev")dev という値かどうか判定できます..また,環境変数であれば,環境変数ENV=dev とセットしたあとに同様に p("ENV").contains("dev") で判定可能です.

『実践 Rust 入門』

思い返すと、Rust をはじめて知ったのは2年前でした。ある Googler が記事の中で Rust に対して惜しみない賛辞を送っていた記事でした。私はこの記事に大変共感し、感銘を受けました。Rust がやりたくなった。

当時の私は、金融業界で使われるリスク管理計算機の開発に携わる開発者でした。当時は Java で計算機の業務ロジックや金融工学計算のロジックを書いていました。その業務を日夜送る中で、複雑化した並列処理計算や並行処理計算にまつわる多くの障害に直面しました。それらは原因がよくわからないけれども、並列・並行処理起因であることだけはわかる障害でした。

並列処理・並行処理で苦しめられた経験がなかったとしたら、Rust という言語に興味をもたなかったかもしれません。当時そういった苦しみを感じていた私は大きく感銘を受けました。プログラミング言語の処理系の力でこれほど多くの問題を解決できるのか、と思いました。プログラミング言語ってすごいですね。それが私の Rust との出会いでした。

Rust は私のプログラマとしての実力を格段に伸ばしてくれたと思います。Rust をやる上では避けられないメモリ周りの知識。あるいは関数型プログラミング由来のパターンマッチングやコンビネーターで実装をつなげていくスタイル。型クラス、静的ディスパッチ。ゼロコスト抽象。多くのプログラミング言語のベストプラクティスをふんだんに取り込んだ言語だからこそ、多くの学びがいに満ちていました。Rust を学習し使いこなすに際し、多くの用語を調べ、概念を苦労して理解し、吸収し、成長させてもらったなという気持ちです。学習コストとはそういうものだと私は思います。いわゆる No Pain, No Gain です。

あれから2年経ちました。Rust は当時とは比べ物にならないほどに流行の兆しを見せ始めています。海外では、StackOverflow の愛され言語ランキングにおいて、ここ数年つねに1位を取り続けています。Reddit でも、Rust に関する記事を見ない日はありません。

日本では、日本語訳された資料や文献がとても増えています。私は当時、Rust の多くを英語で勉強しました。書籍は当然ありませんでした。ネット上には資料はあったけれど。今は、日本語の、それも日本人の書いた書籍が出る時代になりました。感慨深さを覚えます。

その矢先、とてもいい本が出ました。

実践Rust入門[言語仕様から開発手法まで]

実践Rust入門[言語仕様から開発手法まで]

κeen さんより新刊を頂戴し、『実践 Rust 入門』を一足お先に読ませていただきました。ありがとうございます。日本の Rust ユーザーにぴったりな本だと思います。現場で「ここがかゆい」と思うところによく手が伸びていると思いました。これは、普段から Rust を使用している現場の達人にしかなしえないわざでしょう*1。私はこの巨人の肩に積極的に乗っていきたいと思いました。

『プログラミングRust』では、どういった機能が Rust に用意されているのかについての説明に多くの紙面を使っていますが、本書ではどういった機能があるかに関する説明に加え、各機能をどういった場面で使用したらよいのかについても言及されていることが多いです。 たとえば、Box<T> 型について説明する際、本書では次のように記載されています。

Box ポインタは以下のような場面で使われます。

コンパイル時にデータサイズが決まらない型を扱うとき。たとえば再帰的なデータ構造を実現するにはBoxやRcのような値を所有するポインタが必要になる。

・大きなデータをコピーすることなく、その所有権を他者へ移動したいとき。

・トレイトオブジェクトを作成したいとき。

Rust をはじめられる方の中には、もともと C/C++ ではなく、 JavaPython といった処理系が優秀で細かいメモリ制御をほとんど必要としない言語を触っていた方もいらっしゃるかと思います。私もその一人でした。他言語出身者からすると、Rust には、主にメモリ周りの機能に「どう使ってよいのかいまいちイメージがつかないもの」があります。それらの疑問に、本書はよく答えてくれています。私も読みながらはじめて用例を知ったものがありました。

本書は「基礎編」と「実践編」にわかれています。とくに9章からはじまる実践編は、実際にアプリケーションを作る章なのですが、「パーサー」「パッケージ」「Web アプリケーション」「FFI」と続きます。FFI が載っている本は、私の知る限りではありません。私も FFI を使った経験はなく、本書のチュートリアルをぜひこなしてみたいと思いました。

これから Rust をはじめるユーザーや、すでに使っているものの理解を深めたいユーザーにはうってつけの一冊であること間違いなしです。κeen さんもご自身でブログに本に関する話を書いているのでそちらもご覧ください。また、詳細な目次。個人的には、仕事で Rust をはじめるとなったときに「とりあえずこれを読んでおけ!」と自信をもって言える一冊が出版されて、本当に嬉しく思います。

実践Rust入門[言語仕様から開発手法まで]

実践Rust入門[言語仕様から開発手法まで]

*1:私は Scala ユーザーなので、Scala 界隈のアナロジーを用いて本書を説明しておくと、『プログラミングRust』がコップ本だとしたら、本書は『実践 Scala 入門』にあたるといえるでしょう。

社会人4年目が終わった

先週で社会人4年目が終わり,今週から社会人5年目になりました.3年目で新卒入社した金融の会社を辞め,今は2社目に1年半弱在籍しています.振り返りをしておきたいと思います.

ちなみに私は社会人になってからプログラミングをはじめたので,プログラミング歴=社会人歴です.

できるようになったこと

テックリードになった

職位はそうなりました.前々からやりたいと思っていたのでよかったと思います.

うちのチームは,私とは別にエンジニアリングマネージャー的な人がいます.面談や全体の開発方針などはその人が決める構図です.なので,私は技術導入の責任と,製品の品質に対する品質,障害時の説明責任を負っています.

1からアプリケーションを作ってリリースできるようになった

アーキテクチャを1からデザインして,なおかつそれを実装する業務に関してはこれまでもやってきました.なのでとくに伸びてはいません.

一方で,実際に AWS に EC2 を立てて本番リリースする部分や,CircleCI を設定して CI/CD できるようにするといった部分は,3年目まではほとんどやってこなかったのであまりできませんでした.

4年目ではチャンスに恵まれ,CI/CD 周りの仕事をできました.今年習得したスキルの中でもっとも大きかったです.

関数型プログラミングに明るくなった

社内で Scala の cats 勉強会が開かれたのが大きかったです.cats には関数型プログラミングの説明兼 cats の使い方ガイドみたいな本があるのですが,その本を通して関数型プログラミングの手法を用いたアプリケーションのデザインを磨けました.

私は Java 出身でしたので,どうしてもデザインが Java によってしまっていました.それは Scala を使う上では決して悪いわけではありません.しかし,関数型プログラミングを勉強していくうちに,そこによく登場する手法を用いると,より安全で可読性の高いコードを書けると気づきました.ただ,実際のアプリケーションにどう適用したらよいのか,いまいちイメージがついていませんでした.

cats の本を通して,その本に掲載されているふんだんな実用例をもとに,おおよそデザインに関するエッセンスを掴めました.

Pros/Cons を考えながら技術選定できるようになった

今まではあまり深く考えずに好きなものを好きなだけ使うor自前で作ってしまえ,といったスタンスだったのですが,だいぶ技術選定の際に理性を働かせられるようになりました (笑).

技術選定にはいくつか基準があると思いますが,たとえば:

  • チームのスキルセットを鑑みて,ここまでのレベルは扱えるだろうと予想して技術を使う
  • あるいは,チームのスキルセットとあるべき姿を照らし合わせて,ここのスキルセットはチームメンバーに欲しいからあえてこの技術を使おう/やめておこう
  • ライブラリのリリースノートを見て,どの単位でリリースしているのかや,そもそもちゃんと説明責任を果たす作者なのかを確認して,このライブラリを使おう/やめておこう
  • Issue/PR に対する作者の対応を見て,このライブラリを使おう/やめておこう
  • そのプログラミング言語の日本におけるコミュニティの対応状況や盛り上がりを見て,使おう/やめておこう
  • 今後5年くらいは,こういう流れになっているだろうから,この技術を使おう
  • 採用市場にこの技術を扱えるエンジニアが多い(あるいは,これから増えてくる)から,この技術を使おう

こういったことを考えながら,技術選定できるようになってきたかなと思います.もちろん,その技術を好きではあるものの,実際に使用するのは封印しなければならない技術もあります.Rust とか.我慢がきくようになったともいいますね (笑).

低レイヤーの話がかなりわかるようになった

実際に自分でいくつかコンパイラを実装しました.また,会社のゼミ中にネットワーク周りの調査や実装を行いました.それによって,低レイヤーの知識に実感が伴うようになりました.

知識にはいくつかの習熟段階があります.まず,本を読んで概念を理解すること.次に,実際に使ってみて概念を肌感で覚えること.最後に,自身の中で得た知識を体系的に整理し,人に教えること.

これまで低レイヤーの知識に関しては,本を何冊か読んで「概念的には知っている」状態の知識がほとんどでした.しかし,いくつか手を動かしたことによって実感を伴いました.引き続きやっていきです.

Go 言語を書けるようになった

これはちゃんとアプリケーションのリリースまで含めてやったので,書けるといっていいはずです (笑).

最近,弊社にインターンに来る子や新卒の子が,ハッカソンインターンシップ中に Go 言語を多く使うようになってきました.中には8割の参加者が Go 言語を使用したインターンシップもありました.

当時,私自身はあまり Go 言語に関心がなく (というのも,Go 言語の代替で使える言語をいくらか使えるので),今後も触る予定がなかったのです.しかし Go 言語をあまり触っていないがために,インターンシップの評価者として入ったとき,的確な評価を下せず歯がゆさを感じる場面がありました.なので時間を取って勉強してみました.1週間くらいでかなり書けるようになりました.

その後,新規事業で Go 言語を使う機会が訪れました.プロダクションでも無事 Go 言語を使えました.

ただ,普段は使わないので最新情報のキャッチアップが課題ですね.もっとも,優先度は高くないのでタイムラインで見たものをチラッと見る程度で十分だとは思いますが.

Rust 関係での登壇

いくつかしました.Rust は触り始めてからもう2年近く経ちますね.昨今は国内・海外ともに採用事例が増えてきている上に,Reddit を見ていると Rust はよくバズっているので,注目度の高まりを肌で感じられるようになってきましたね.

コンテナのうれしさがわかるようになった

いろいろ苦労しましたがやっと.これまで,私の所属しているプロダクトにはコンテナがほとんど採用されていませんでしたが,いくつか機会があって導入しました.

やろうと思っていたけどなかなか手が伸びなかったもの

Linux そのものに関する話

アプリケーションエンジニアとしての時間が長いので,普段から macOS x IDE で大半の作業を行います.なので用意されているコマンド,OS の仕組み等々込で Linux が結構苦手でした.

前職は Windows だったため,転職当初はほとんど Linux をよくわかっておらず,障害時などにとくに困っていたのですが,1年半経ってある程度操作には慣れたように思います.しかし,まだ周りのエンジニアを見ているとその平均的な水準には届いていないかなと思っています.

が,あまり関心が向かないのか,ほとんど手をつけられませんでした.

アルゴリズム

概念的には理解できるけど,いざ実装する・使いこなして問題を解くとなるとちょっと苦手ですね….慣れていないだけだと思います.

これも去年何冊か本を買っては見たものの,結局関心が向かないのかなんなのかで,あまり目を通せませんでした.

ただアルゴリズムはやっておかないとこの先外資系に行きたい場合などに苦労することになりそうなので,まとまった時間をとって勉強しておきたい.TopCoder をやるなどして強制的にやらないといけない環境に置いたほうがよいかもしれません.

AWS の認証,ネットワーク周り

何度か体系的に知識を得て整理しようと思っていたのですが,結局関心が向かないのかなんなのかでやっていません.が,ついに今週体系的な知識の必要性を感じる場面があったので,これに関してはようやくモチベーションが湧いてきました.やります.

データ分析・SQL

やれば楽しいに決まっているし,実際チャンスもたくさんあるチームなのでやろうと思えばやれる!と1年くらい思っていましたが,食指が動きませんでした.毎年新卒のデータアナリストが入ってくるたびに簡単な練習問題を出しているので,今年は私もそれに参加させてもらおうかなと思っています.

あと SQL はいまだにあまり伸びてない.ORM 畑育ちなので,生の SQL はまだ抵抗がありますね.これも,新卒のデータアナリストと一緒に勉強しようかなと思います.

5年目で手を伸ばそうと思っているもの

仕事面

理想像はいるの?

これ,私と一緒に働いている方は驚くかもしれませんが,同じ部署内にいます.その人と比べて自分に足りないところはおおよそ次の通りかなと思っているので,何年かかけてのんびり力を伸ばせていけたらいいなと思っています.

伸ばしたいスキル

  • Linux,シェル: 基礎教養だと思うので.
  • 分散処理関係の話にもう少し詳しくなりたい←漠然としているのはまだ何があるかわかっていないからです
  • AWS のネットワーク,認証周り: 概念の整理と理解,ならびにどこをどう編集したら自分のやりたいことが実現できるかをスムーズに手順立てできるところまで.
  • データを前処理して Python で可視化するところまでできるようになりたい.

プライベート

プログラミングやコンピュータそのものが好きだとこの4年目を通して気づいたので,それ関連をプライベートでも進めていきたいと思っています.

Functor,Applicative Functor について勉強したので整理してみる

最近 Functional Programming in Scala の勉強会をずっとしているのですが,ようやく12章の Applicative Functor に入りました.ところが,急に登場した Applicative という概念がいまいち勉強会の時点ではつかめておらず,少し頭の中を整理したいと思ったので簡単にまとめてみようと思います.あまり体系的にまとめるつもりはありません.自身の理解のメモとしてインターネットに放流しておく予定です.

(Applicative Functor はアプリカティブファンクタと以下書きます.日本語ググラビリティのためにそうします.他のこれ関連の用語も同様にカタカナで表記します.)

なお,モノイドやモナドは登場させません.これには意図があって,『すごい Haskell たのしく学ぼう!』という本では,モナドの説明なしにアプリカティブファンクタの説明をじっくり行っていたためです.この説明はわかりやすいなと思いました.今回はこの本の11章を参照しながらいろいろと考えていきます.

勉強会で使用している本はこちら.

すごいHaskellたのしく学ぼう.これは関数型プログラミングの理解の助けになる本だなと思います.

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

コードそのものは Scala で書きます.すごい Haskell 本のソースコードを読みつつ,Scala で書くとこうなるかなと翻訳して書いています.ただ厳密にはちょっと違いそう.

ファンクタとは

ファンクタとは「関数で写せるもの」のことを言います.要するに写像 (map) です.といったところで,???が頭に浮かぶかもしれません.さっそくですが実装を見てみましょう.

trait Functor[F[_]] {
  def fmap[A, B](fa: F[A], f: A => B): F[B]
}

これを見ると一目瞭然なのですが,F の中身の型を A から B に変えているものですね.F にはたとえば OptionList といった型が入ります.この外側の型を保ちながら,内側の型を別の型に変換することをしてくれる抽象的な概念をファンクタと呼びます.難しくいうと,値があるかないかという Option の文脈を保ちながら,型を変換しているということです.

ファンクタは1引数しか受け取ることができません.なので,Either のような2つの型変数をもつ型をファンクタにするには,一度部分適用をして,あと1つ型変数を引数に取るだけの状態にする必要があります.

さて,ファンクタのざっくりとした説明はここまでです.11章では lift などの概念も実は登場してきていますが,今回は省きました.

アプリカティブファンクタとは

さて,欲しくなる場面ですが,たとえば次のようなコードがあったとしてみてください.

val f = n => n * 3
val someF = Some(f)
val some5 = Some(5)
// いい感じに Some(f(5)) をしたい

Some(f) の f に Some(5) の5を適用したいとなった場合にどうしたらよいのでしょうか.普通のファンクタを扱う限り,これはちょっと難しそうです.というのも,普通のファンクタでできるのは,「通常の関数で」「ファンクタの中の値を」写すことだけだからです.上述したように,一度 Some の中の値を両方とも取り出して,再度 Some に詰め直すという作業をするか,あるいは fmap を実装しているのであればそれを2回適用すればよさそうに見えます.

ただ,もう少しスマートにやる方法があります.それがアプリカティブファンクタという概念です.コードを見てみましょう.

trait Applicative[F[_]] extends Functor[F] {
  def pure[A](a: => A): F[A]
  def <*>[A, B](fa: => F[A], f: F[A => B]): F[B]
}

要するに,Fの中に入った関数を元の F に適用 (apply) して,その結果値を受け取ることができるものです.それゆえに applicative …?ただ,こうすることで,ファンクタでは1引数関数でしか処理できなかったものを,アプリカティブファンクタでは2引数関数で処理できるようになります.これはファンクタよりも強力になったことを意味します.

pure というのは,なんでもない a という値を受け取ると,それを F の中に入れて返すものです.<*> は,見たとおりで F という型の中で関数を適用してくれるスグレモノです.

ところで,Haskell の Applicative にはさらに便利な関数があるようです.それが,liftA2 という関数です.liftA2 は,「通常の2引数関数を,2つのアプリカティブ値を引数に取る関数に昇格させる」という機能を持っています.実装を見てみましょう.

trait Applicative[F[_]] extends Functor[F] {
  def pure[A](a: => A): F[A]
  def <*>[A, B](fa: => F[A], f: F[A => B]): F[B]
  def liftA2[A, B, C](a: F[A], b: F[B], f: (A, B) => C): F[C] // ←追加された
}

実装のとおりです.ここまでそろうと,ようやくアプリカティブファンクタを実装することができます.実装してみました.

trait Applicative[F[_]] extends Functor[F] {

  def pure[A](a: => A): F[A]

  override def fmap[A, B](fa: F[A], f: A => B): F[B] = this(fa, pure(f))

  def <*>[A, B](fa: => F[A], f: F[A => B]): F[B] =
    liftA2(f, fa, (_: A => B)(_: A))

  private def apply[A, B](fa: => F[A], f: F[A => B]): F[B] = <*>(fa, f)

  def liftA2[A, B, C](a: F[A], b: F[B], f: (A, B) => C): F[C] =
    this(b, fmap(a, f.curried))
}

コンパイルも通ったしたぶん大丈夫…!private defapply を追加したのは,単純に <*> を使おうとするとうまいこと中置記法できなくてかっこ悪いと思っただけです.あんまり深い意図はありません.

ところで <*>fmapliftA2 が循環参照しているので,最終的にこの Applicative を使う側でどこかを実装してやる必要があるのでしょうか.Functional Programming in Scala の中でも循環参照している実装例が出てきたのですが,使う側で個別実装したら大丈夫だったので,たぶんそういうことなのだと思っています.

ということで,少し実装をしてみましょう.たとえば Maybe という型があったとします (Haskell から拝借).

sealed trait Maybe[+A] {
  def <*>[B](f: Maybe[A => B])(implicit F: Applicative[Maybe]): Maybe[B] = F <*> (this, f)
}

object Maybe {
  case class Just[+A](a: A) extends Maybe[A]
  case object Nothing extends Maybe[Nothing]
}

object applicatives {
  implicit def maybeApplicative: Applicative[Maybe] = new Applicative[Maybe] {
    override def pure[A](a: => A): Maybe[A] = Just(a)
    override def fmap[A, B](fa: Maybe[A], f: A => B): Maybe[B] = fa match {
      case Just(a) => pure(f(a))
      case Nothing => Nothing
    }
    override def <*>[A, B](fa: => Maybe[A], f: Maybe[A => B]): Maybe[B] =
      f match {
        case Just(something) => fmap(fa, something)
        case Nothing         => Nothing
      }
  }
}

これであっているのかは若干怪しいところですが (ちょっと美しくない実装な気もする),おおよそのイメージは掴んでもらえるはずです.実行して試してみましょう.

object Main extends App {
  val just5 = Just(5)
  val justF = Just((n: Int) => n * 3)
  implicit val maybeApplicative = implicitly[Applicative[Maybe]](applicatives.maybeApplicative)
  println(just5 <*> justF)
}

これを実行すると,結果は

> Just(15)

と返ってきます.やりたかったことが実現できましたね.

まとめ

  • ファンクタは map のことであり,関数の写しである.F の中身を単に取り出して変換する.
  • アプリカティブファンクタは Haskell だと <*> であり,2つの F を受け取って,両者を引数に受け取る関数を適用して結果値を取り出したり,あるいは,F の中に入った関数を F の中で適用して結果値を受け取ることができる.
    • まとまながらなるほどなと思ったんですけど,これがゆえに Functional Programming in Scala では Traverse[F[_]] の説明に入ってゆくのですね.

しかし,とくにアプリカティブファンクタの方がまだまだ理解が100%になった気はしませんね.つかめるまでもう少しエクササイズをしたいと思います.

Run Length Encoding

『問題解決のPythonプログラミング』という本を読んでいたら出てきたエクササイズです.おもしろそうだったのでやってみました.もともとその章のお題だった配列をぶん回すという方針に従って,それを応用して今回はやってみました.各文字に対してカウンタをもつ HashMap なり Dictionary をもたせるとよりシンプルになりそうな気がします(Python での Dictionary の書き方がまだ習得できておらず面倒でやってない).

Disclaimer

Python 素人なのでもしかすると罠を踏んでいるかもしれません.

Run Length Encoding とは

たとえば次のような文字列があったとします.

BWWWWWBWWWW

この文字列を Run Length Encoding をもちいて圧縮すると次の文字列に圧縮されます.

1B5W1B4W

文字を1つ1つ分解して,重複して出てきた回数を文字の左に表示することで表現しています.当初は 11 byte (使用する言語により異なりそうですが,今回は便宜上1文字=1 byteと計算します) だった文字列の確保領域が 8 byte に減少しています.

実装してみる

さて,これを Python で実装してみました.

def run_length_encoding(strings) -> str:
    caps = list(strings)

    if len(caps) == 0:
        print('empty list')
        return ''

    caps = caps + ['#EOF']
    counter = 1
    encoded = ''

    for i in range(1, len(caps)):
        if caps[i] == caps[i-1]:
            counter += 1
        else:
            print(counter, caps[i-1], sep='', end='')
            encoded = encoded + str(counter) + caps[i-1]
            counter = 1  # counter reset

    return encoded

ポイントは #EOF という文字列を最後に追加しているところですね .caps という配列をインデックスで回して,前後の文字列が一致している限りはカウンターを動かすということをしています.異なった瞬間にプリントアウトしつつカウンターを初期値に戻しています.文字列が異なることを reduce のトリガーとするため,最後の #EOF を入れないと,最後の方の文字列のカウントがうまいこといきません.[*1]

さて,問題には decode もしろと書いてあったので decode もしましょう.1B5W1B4W という文字列を元に戻していきます.

def run_length_decoding(strings) -> str:
    caps = list(strings)

    if len(caps) == 0:
        print('empty list')
        return ''

    decoded = ''

    for i in range(1, len(caps)):
        if caps[i].isalpha():
            char = caps[i]
            count = caps[i-1]
            decoded = decoded + char * int(count)

    return decoded

また同様に,まずは文字列を分解して配列に直します.配列をインデックスで回します.アルファベットの箇所に到達したら,今回のエンコード方式ならばすぐ左に数字があるはずなので,その数字を取り出して回数分文字列を生成します.Python だと,文字列 * int で int の数値分文字列を生成できます.これを活用していきましょう.

アルゴリズムのカタログというよりは,目の前の問題をプロはどのように解いていくのか?という部分の解説に主眼を置いたいい本だなと思います.MIT の授業かなにかが書籍化したっぽいですね.私は CS の教育は受けていないので,こういう本が非常に助かります.Python なのも嬉しい.Python ならわかる.

*1:この文字列,非常に危なっかしいですね.別のトークンを用意してあげる必要はありそうですが,まあ今回は本書の手法に従いたいのでそうしました.Map を使用すればこのようなことは必要ありません.

Servo の開発に出てくる highfive について

小ネタもうひとつ.

Servo の開発をするとお世話になるのが highfive という bot です.新規コントリビュータの人が参加しやすいように作られた bot だそうです.アイコンかわいい.

github.com

お世話になる場面は下記の2つです.

  • Issue を自身に割り当てる
  • PR のラベル管理

Issue を自身に割り当てる

Servo はまだまだ新機能の開発ややり残したタスクなどが Issue としてたくさん上がっています.これはすべてのデベロッパーに開放されており,やろうと思えばいつでも Issue に取り組むことができます.その最初のタッチポイントとして highfive が登場します.

自身が担当したい Issue に次のようなコメントを残すと,自身にその Issue がアサインされます.

@highfive assign me

するとこんな感じで,Issue にラベルがつけられて自身に Issue がアサインされます.

f:id:yuk1tyd:20190303235858p:plain

f:id:yuk1tyd:20190303235912p:plain

PR のラベルの管理

Servo では,S-awaiting-review (レビュー待ち) といったラベルで PR の状況を管理しています.ラベル管理はほぼすべて highfive が行っていますね.できる子…!

余談ですが,最初に PR を投げるとまず自動的に S-awaiting-review というラベルが付与されます.その後,もしコードの修正が必要なようであれば,S-needs-code-changes というラベルが付与されます.さらにテストが落ちていたりすると,highfive が S-test-failed というラベルが付与されます.落ちたテストが修正コミットにより再び通ると highfive が S-awaiting-review を付与し,S-test-failed のラベルを取り除きます.

このあたり,人力でやると運用がカオスになってしまいがちなので,こういった bot に自動化させておくというのは非常に正しいなと思いました.

highfive 自身

ちなみに Highfive はソースコードが公開されています.

github.com

上小ネタでした.

Servo の開発で出てくる bors-servo について

Mozilla 関係のプロダクトにコントリビュートするとよく見かける (?) ,bors というボットがいます.たとえば,普段コントリビュートするみなさんもこういったコメントを見たことがあるかと思います.

@bors-servo r+

普段は自分でキックすることはないので,基本レビュワーの方々に任せておけばいいかな〜というスタンス(そしてコケたら対応すればいいやのスタンス)なのですが,せっかくの機会なのでいろいろ調べてみることにしました.

そもそも bors とは

裏側は Homu というツールが動いています.Homu とは,リポジトリのコードのすべてのテストがいつも通っている状態を自動的にキープしてくれるツールのことです.自動テストツールといったところでしょうか.

個人の方が作っていた OSS です.ただ作者の方はもう GitHub 上ではアクティブではないため,Servo が fork してメンテナンスして使っているようです.

github.com

github.com

公式ガイドによると次の手順で動作するようです.

  1. 開発者の working branch はいつもどおりアップロードされる (要するに fork なりした branch から普通に commit & push するということですかね).
  2. レビュワーがコードをチェックして大丈夫そうならば,Homu に対してメッセージを送る (Servo なら @bors-servo r+ などというコメントを見ますね).
  3. Homu は master ブランチにマージを行う (が,それは本当の master とは別になっていて auto と呼ばれるもの).
  4. サーバーのクラスタ内で,すべての OS に対するテストが行われる.
  5. クラスタが OK を返すと,Homu は auto を master にコピーしたと返答する.
  6. クラスタが NG を返すと,Homu はそのエラーレポートを返す.何もしない.

コマンドのチートシート

詳しくはこのガイドに載っています.

build.servo.org

よく見るものだけ軽くまとめておきます.

  • r+: PR の承認を意味します.これが出るとだいたい OK 感ある.bors の種々のテストが行われた後,マージされてその PR は Close となります.
  • try=xxx: 承認なしでテストだけ走らせることができます. PR を投げた直後に飛ばしていました.try → テストが OK → r+ の流れな気がします.
  • retry: 文字通り retry します.普通にテストが別のツールで通っているのに落ちることがあり,変だなと思ったら投げているようですね.

Issue は自動的に閉じられる

担当した Issue は,bors-servo が最後に自動的に閉じて後片付けまでしてくれます.これなら閉じ忘れも起きませんね.

Servo の開発においては,あらゆることが自動化されていて,OSS とはこういう感じなんだなあという勉強になることが多いです.

小ネタでした.