Infinito Nirone 7

白羽の矢を刺すスタイル

つよいレンズをもっていろいろ写真を撮ってきた

つい先日、Canon EF 24-70mm f/2.8L II USM というつよいレンズを購入しました。

目的はカンファレンス会場内でバッチリ撮影できるレンズをもつことなわけですが、素振りも必要ということで、RedBull Air Race に誘ってもらったのを機に写真を撮ったのと、DroidKaigi roadshow 福岡へ行ったのを機にイベントの撮影をしつつ翌日門司港でフラフラしたときに写真を撮ってきました。

RedBull Air Race

今回でシリーズそのものが終了となる RedBull Air Race。日本人パイロットが活躍している事は知っていたものの、競技のルールなどは予備知識ゼロで行きました。会場での解説を聴きながらでもルールが理解できるようになっていていいですね。

f:id:KeithYokoma:20190907135119j:plain

24-70mm なのでもちろん寄れないんですが、飛行機にフォーカスを合わせつつ観戦している人々をフレームに入れるにはちょうどいい感じでした。

f:id:KeithYokoma:20190907141904j:plain

f:id:KeithYokoma:20190907141906j:plain

門司港

戦時中、祖父が輸送船の乗組員だったころに来たことがあるという門司港に行ってみようと思って来てみました。ほぼ何も調べずに来てしまいましたが、地ビール醸造所があったり、唐揚げフェスをやっていたり、観光客向けに展望デッキがあったりと、短時間でもスッと楽しめる街でした。 町並みもレトロな感じでいいですね。

f:id:KeithYokoma:20190915140953j:plain

門司港に来るのが初めてなので、関門海峡を眺めるのも初めてです。

f:id:KeithYokoma:20190915141306j:plain

クラシックカーのあつまりもありました。かっこいいですね。

f:id:KeithYokoma:20190915141620j:plain

f:id:KeithYokoma:20190915143650j:plain

f:id:KeithYokoma:20190915144344j:plain

f:id:KeithYokoma:20190915144917j:plain

ゴンチャではなくコンチャが出店してました。

f:id:KeithYokoma:20190915150346j:plain

f:id:KeithYokoma:20190915152657j:plain

f:id:KeithYokoma:20190915155734j:plain

帰りは飛行機をいくつか。

f:id:KeithYokoma:20190915181141j:plain

f:id:KeithYokoma:20190915181148j:plain

f:id:KeithYokoma:20190915181150j:plain

f:id:KeithYokoma:20190915212019j:plain

What's next?

望遠のつよいやつほしい

Kotlin の enum class とシリアライズで気をつけること

Kotlin の enum クラスは、ほぼ Javaenum と同じように扱うことができます。

次の Kotlin コードは FOOBAR という Hoge 型の定数を定義しています。

enum class Hoge {
  FOO,
  BAR
}

enum クラスで定義した定数は String への変換 (nameメソッド) と、 String からの変換 (valueOfメソッド) をサポートしているので、簡単にシリアライズ・デシリアライズができます。またenum クラスに振る舞いを記述できるので、フィールドをもたせたり、メソッドを呼び出したりするコードも動きます。

enum class Hoge(val flag: Boolean) {
  FOO(true),
  BAR(false);

  fun getSomething(): String {
    return "Hello, world!"
  }
}

メソッドは abstract で定義したり、インタフェースを実装したりもできます。この場合、個々の定数の定義で具体的な処理を記述します。

enum class Hoge {
  FOO {
    override fun getSomething(): String {
      return "Hello, world!"
    }
  },
  BAR {
    override fun getSomething(): String {
      return "Hi, world!"
    }
  };

  abstract fun getSomething(): String
}

ここまでがおさらいです。

ここで次のように String? から任意の型のオブジェクトへ変換する Generic なメソッドを考えます。ストレージに書き込んだ文字列から期待する型への変換を行うようなメソッドです。

@Suppress("UNCHECKED_CAST")
fun <T : Any> String?.convert(fallback: T): T {
  return when (fallback) {
    is String -> {
      this ?: fallback
    }
    is Int -> {
      this?.let {
        Integer.valueOf(this)
      } ?: 0
    }
    is Enum<*> -> {
      this?.let {
        val method = fallback.javaClass.getDeclaredMethod("valueOf", String::class.java)
        method.invoke(null, this)
      } ?: fallback
    }
    else -> {
      throw IllegalArgumentException("")
    }
  } as T
}

このメソッドを、次のように標準入力で与えられた文字列からデシリアライズする目的で使ってみましょう。標準入力で FOO と入力すると、正しく定数に変換されます。

fun main(args: Array<String>) {
  println(readLine().convert<Hoge>(Hoge.FOO))
}

enum class Hoge {
  FOO,
  BAR
}

では次の場合はどうなるでしょうか。Hoge には abstract メソッドが定義されています。

fun main(args: Array<String>) {
  println(readLine().convert<Hoge>(Hoge.FOO))
}

enum class Hoge {
  FOO {
    override fun getSomething(): String {
      return "Hello, world!"
    }
  },
  BAR {
    override fun getSomething(): String {
      return "Hi, world!"
    }
  };

  abstract fun getSomething(): String
}

上記のコードの場合、FOO を標準入力に入れると次のようなエラーで異常終了します。

Exception in thread "main" java.lang.NoSuchMethodException: dev.keithyokoma.Hoge$FOO.valueOf(java.lang.String)
    at java.lang.Class.getDeclaredMethod(Class.java:2130)
    at dev.keithyokoma.MainKt.convert(Main.kt:22)
    at dev.keithyokoma.MainKt.main(Main.kt:6)

この挙動の差は生成されるバイトコードの違いを見ると分かります。

メソッドの定義がなかったり、実装を enum クラスの宣言に書く場合は次のようなバイトコードが生成されます。

public final enum dev/keithyokoma/Hoge extends java/lang/Enum  {
  // access flags 0x4019
  public final static enum Ldev/keithyokoma/Hoge; FOO
  // access flags 0x4019
  public final static enum Ldev/keithyokoma/Hoge; BAR
}

一方で、abstract メソッドやインタフェースのメソッドの実装を個別の定数で行う場合は次ようなバイトコードが生成されます。

public abstract enum dev/keithyokoma/Hoge extends java/lang/Enum  {

  // access flags 0x18
  final static INNERCLASS dev/keithyokoma/Hoge$FOO dev/keithyokoma/Hoge FOO
  // access flags 0x18
  final static INNERCLASS dev/keithyokoma/Hoge$BAR dev/keithyokoma/Hoge BAR

  // access flags 0x4019
  public final static enum Ldev/keithyokoma/Hoge; FOO
  // access flags 0x4019
  public final static enum Ldev/keithyokoma/Hoge; BAR
}

なにやら増えていますね。内部クラスとして Hoge$FOOHoge$BAR があるように見えます。そして実行時のエラーでは、Hoge$FOO に対して valueOf メソッドを探したが見つからなかった、というメッセージが出ています。

enum クラス内の個別の定数で実装を定義する場合 Hoge.FOOHoge$FOO 型で、Hoge.BARHoge$BAR 型として解釈されます。これらはすべて単なるクラスなので、Generic な型変換コードの val method = fallback.javaClass.getDeclaredMethod("valueOf", String::class.java)valueOf メソッドを見つけられません (Hoge$FOO 型の Hoge.FOO が格納された fallback 変数に対し valueOf を探そうとするが、valueOf メソッドは Hoge のクラスメソッドとして定義されるので見つからない)。

Hoge$FOO 型と Hoge$BAR 型はどちらも Hoge 型のサブクラスになっているので、次のように修正すると期待通りの動作をします。 fallback の型が enum かどうかを判別し、enum でなければその親クラスから valueOf メソッドを探します。

@Suppress("UNCHECKED_CAST")
fun <T : Any> String?.convert(fallback: T): T {
  return when (fallback) {
    // ...
    is Enum<*> -> {
      this?.let {
        val clazz = fallback.javaClass
        val method = if (clazz.isEnum) {
          clazz.getDeclaredMethod("valueOf", String::class.java)
        } else {
          clazz.superclass.getDeclaredMethod("valueOf", String::class.java)
        }
        method.invoke(null, this)
      } ?: fallback
    }
    // ...
  } as T
}

コンパイラの吐き出すバイトコードまでを考慮しないといけないコードになってしまいました (enum クラスは文法上継承できないはずなので、isEnumfalse のときに superclass を参照しにいくのは一見とても奇妙)。

ちなみに、Java で同様のコードを書いて検証してみたところ、Java でも同じ挙動をしました。

もし、型パラメータの T から直接 Class クラスを参照できれば、このような回りくどい記述は必要ありません。 次のコードでは正しく valueOf メソッドが探せます。

inline fun <reified T : Any> String?.convert(fallback: T): T {
  return when (fallback) {
    // ...
    is Enum<*> -> {
      this?.let {
        val method = T::class.java.getDeclaredMethod("valueOf", String::class.java)
        method.invoke(null, this)
    } ?: fallback
  }
  // ...
  } as T
}

2019/09/03 16:00 追記

型パラメータ TEnum<T> に限定できる場合は、リフレクションは不要になり、代わりに enumValueOf を使います (thank you @red_fat_daruma !)。

inline fun <reified T : Enum<T>> String?.convert(fallback: T): T {
  return this?.let { value ->
    enumValueOf<T>(value)
  } ?: fallback
}

カンファレンスとハイエースと筋肉

カンファレンスとハイエース

カンファレンスを運営するにあたって、当日使用する物資の運搬は非常に重要です。登壇者の後ろに配置するバックパネルや会場案内図、立て看板など、重量があったり、縦横の大きさがそこそこあるものなどの運搬について、一般的な配送方法(宅配便など)では運搬できなかったり、業者に依頼する量ではないにしても電車等での公共交通機関での運搬は不可能な状況があったりします。このような場合に、自分たちで物資を運搬する手段を確保するというオプションがあります。

自分たちで物資の運搬をする手段をとる場合、前提として、大きなサイズの車を運転できる人の確保と、物資輸送にあたって重量物や大きな物を運ぶ体力・筋力の確保が要求されます。 前者については実際に運搬する物資の物量に依存しますが、軽ワゴン車で足りる場合から、ハイエースやキャラバンなどのバンですら容量不足で2tトラックが必要な場合まで様々で、免許区分で NG となることもあります。後者については、バン以上の車であれば、運転手・助手席の間にさらにもう一席あるので、3人の体力・筋力が得られます。

2019年現在の普通免許を持っていればバンなら運転可能です。私の免許では5tトラック(最大積載量3t)までなら運転可能ですが、今までで運転したことのある一番大きな車はハイエースやキャラバンなどのバンに相当する車です。カンファレンスでの物資輸送以外に、自宅の引っ越しでハイエースを使って自力輸送をしたこともあります。

この記事では、タイトルにあるとおりバンを用いたカンファレンスにおける物資輸送について、いくらかの Tips を書き残しておこうと思います。

輸送計画

まず物資輸送の計画を立てるにあたって、運転手と車両の確保を考えることになります。 運転手についてはバンの運転ができる人を地道に探すしかありませんが、車両の確保は比較的容易で、レンタカーでバンを借りれます(もちろん、運営チーム内でバンまたはそれに相当する輸送力をもった車両を所有している人がいれば、その人に協力を仰ぐのが最適なはずです)。昨今はカーシェアリングサービスも盛んですが、物資輸送に最適なバンを借りるのであれば、トヨタレンタカーやニッポンレンタカー、ジャパンレンタカー、オリックスレンタカーが大手で取り扱っています。トヨタレンタカーであれば確実にハイエースを借りられます。

レンタカーで物資輸送を計画する場合、借りる時間をどう設定するかが問題になります。レンタカーである以上契約で使える時間に制約があるため、借りる手続きをしてから荷物をピックアップし、荷物を保管する場所まで移動したあとでその荷物を人手で積み下ろしたうえで、給油して返却するまでが計画されなければなりません。レンタカーを借りる店舗ごとに営業時間が異なるので、24時間営業でない場合には特に返却時間に気を配る必要があります(24時間営業であっても、22時以降の返却には料金の上乗せがあったりしますし、借りた店舗とは異なる店舗で返却するワンウェイレンタルに制約があることも多いです)。

レンタカーでもカーナビが標準装備とはいえ、運転者はどういう経路で物資を運搬するかをよく把握し、返却にあたっては店舗の最寄りにあるガソリンスタンドを把握するなど、経路について事前によく調べておく必要があります。また、荷捌き場への入り方はビルごとに異なるため、事前によく認識を合わせておきましょう。

運転

ハイエースやキャラバンなどバンに相当する車両はそのサイズ感から敬遠されがちだったり、用途からして普段遣い向きではなかったりすることから運転したくないと思われがちですが、扱いづらい車ではないように思います。

よく利用されるコンパクトカーやミニバンとの一番おおきな差異はやはり車体の大きさでしょう。車体そのものが長く幅も広いなど、とにかく小回りとは無縁そうなイメージがありますが、その車体の見た目に反して小回りはきくほうです。ミニバンよりも大きいように見えて、実はミニバンよりも小回りがきくなんてこともあります。

バンとその他の車で違うこともいくつかあります。

パーキングブレーキは車種によってまちまちですが、トヨタハイエースならば左手側に引っ張るレバー(ステッキ式)があり、日産・キャラバンであれば左側にフットペダルがあります。ステッキ式は取っ手にボタンが付いていて、パーキングブレーキを解除するときにそのボタンを押しながらすこし引っ張って解除します。 サイドミラーは手動で調整するものがあります。レンタカー屋さんで借りる車の年式などで変わるので、調整が必要なときは出発前に見ておきましょう。 その他、前輪の位置がミニバンとは違って運転席の真下にくるので、ハンドルをきるタイミングに気をつけましょう。もちろん、荷物が空の状態とある程度積んだ状態では重量が違うので、ブレーキをかけるタイミングやらアクセルの踏み方も状況に応じて調整しましょう。

荷物の積み込み

バンは荷室がかなり広く使えます。ただ後輪のでっぱりや両側スライドドアのステップがあったりするので、純粋に長方形のスペースをフルに使えるわけではないことに注意します。 最近のレンタカーではバックカメラがついていて、バックギアに入れるとカーナビの画面がカメラの映像に切り替わるようになっていますが、とはいえミラーから見える視界を遮るほどのたかさまで荷物を積んでしまうのもよくないので、荷物の積み上げは程々に。また角の硬い荷物を積むときは緩衝材で保護するなどして他の荷物や車内を傷つけないようにしましょう。

積み込む量にもよりますが、ハイエースを借りるということはそこそこの物資を積み込み・積み下ろしするはずなので、荷運び人員が必要です。カンファレンス会場の荷捌き場との行き来もあるので、それなりの筋力が求められます。台車がある場合はこれを活用しましょう。レンタカー屋さんで借りることもできます。ただし会場によっては台車の使用に制限(台車を使用していい出入り口やエレベータに限りがあるなど)を設定しているところもあるので、会場のルールに従って運搬します。

重いものがあるときは無理せず複数人でゆっくり運びましょう。とくに腰は大事です。持ち上げる・下ろすときに腰を壊しやすいので、しっかり腰を落として持ちましょう。

まとめ

ハイエースやキャラバンなどのバンは荷物を運搬するために車体が大きいものの、扱いやすさで言えばミニバンよりも小回りがきくなど便利に使えます。荷室も広いのでかなりの物資を輸送できますが、輸送にあたっては人手も必要です。怪我や事故の無いよう安全に十分配慮しつつ荷物を運びましょう。

DroidKaigi の撮影で Canon EF 70-200mm f/2.8L IS USM III をレンタルしてみた

カンファレンスカメラマンの悩みのタネとして、登壇者が遠いので望遠レンズがほしい、でもそうするとどうしても暗くなってしまう(=ブレまくるしいい絵にならない)ので明るい望遠レンズがほしい(=スーパー高くなる)という葛藤があると思います。

かく言う私も EOS 6D Mark II に買い換えてから標準キットの 24-105 しか使ってこなかったので、寄りたいときに全然寄れないし暗いという悩みを抱えていたので、思い切ってカンファレンス用にレンズを借りてみることにしました。

結論しかないんですが、借りて大正解だったし、最初の一枚をパシャッと撮ってプレビューを見た瞬間の高揚感たるや、こんなレンズほしいに決まっとるやろ!!!!という思いを強くしたのでした。

git merge の 3 つの動き

いまや Git なしでは生きていけないくらい Git に支えられている生活を送っていますが、Git のブランチをマージする手段にはいろいろあって、実際 GitHub でもプルリクエストをマージするときには 3 種類の方法から選ぶよう促されます。

git merge

ブランチをマージするコマンドですが、ブランチの状況によって挙動が変わります。具体的には Fast Forward と non-Fast Forward の 2 種類があり、オプションで指定しない限りは Git が勝手に判断してくれます。ここでは、masterから派生したtopic/aブランチをmasterにマージすることを想定したときの動きを説明します。

Fast Forward

topic/aの派生元のリビジョンがmasterの最新リビジョンと一致するときは、単純にtopic/aの最新リビジョンをmasterの最新リビジョンに変えるだけでブランチがマージできたことになります。このように、マージ先ブランチの最新リビジョンをマージ元ブランチの最新リビジョンにするだけでマージが完了することを Fast Forward といいます。

マージ前の状態
マージ前の状態

Fast Forward マージ後
Fast Forward マージ後

git logで歴史を見ると、マージされたtopic/aブランチはmasterの歴史の一部となっていて、別のブランチが存在したようには見えなくなります。

強制的に Fast Forward でマージする場合は git merge --ff-only とします(Fast Forward とならない場合はエラーとしてマージできない)が、オプションなしで git merge する場合のデフォルトの挙動も Fast Forward です(Fast Forward とならない場合は non-Fast Forward でマージしようとする)。

non-Fast Forward

topic/aを派生したあとにmasterに異なるコミットがある場合は、masterの最新リビジョンを変えてしまうとtopic/aを派生したあとに積み上げたコミットが無かったことになってしまうため、Fast Forward でマージできません。そこで、topic/aにあるコミットをmasterに混ぜた上で、マージをしたことを示すマージコミットを作成します。

マージ前の状態
マージ前の状態

non-Fast Forward マージ後
non-Fast Forward マージ後

git logで歴史を見ると、マージされたtopic/aブランチの持っていた歴史は時間順にmasterの歴史に取り込まれつつ、マージコミットによって別のブランチが存在していたこともわかるようになります。

強制的に non-Fast Forward でマージする場合は git merge --no-ff とします(Fast Forward でマージ可能な場合でも non-Fast Forward でマージコミットを作る)。

Squash

これは上記の 2 つとは異なり、明示的にオプションで指定(--squash)しない限りこの挙動にはなりません。

topic/aにある全コミットを1つに集約しmasterにコミットを作るマージのことを Squash マージといいます。git logで歴史を見ると、Fast Forward のようにtopic/amasterの歴史の一部になったようには見えなくなります。Squash マージしたあとでさらにtopic/aにコミットを積み上げ Squash マージをすると、もういちど topic/aの全コミットを1つに集約しなおしてコミットを作ろうとします。

Squash マージ後
Squash マージ後

余談

git rebase

なにかと怖がられがちですが、してはいけないこと*1はとてもシンプルかつたいていどこかでエラーを起こして失敗する*2ので、もっと安心して使えるコマンドです。 GitHub のプルリクエストをマージするときの選択肢に出てくるうちの1つでもあります。

名前のとおり、ブランチの派生元リビジョンを変える(re base)ためのコマンドで、手作業で同等のコマンドを打つとするなら、ブランチを目的の派生元リビジョンから切りなおしたうえで rebase 前のブランチにあるコミットを1つずつ cherry-pick するようなものです。たとえば、masterの歴史とtopic/aの歴史がそれぞれに進んでいるときにtopic/amasterに rebase すると、topic/aの派生元はmasterの最新リビジョンとなり、topic/aにあったコミットは別のリビジョン番号を持って積み直されます。

git pull

リモートリポジトリからコミットを取得するコマンドです。デフォルトでは git fetch かつ git merge の動きをしますが、オプション(--rebase)をつけると git fetch かつ git rebase の動きをします。

たとえば、mastergit pull をすると、Fast Forward できるときはマージコミットは作られず、non-Fast Forward の場合はマージコミットができます。また mastergit pull --rebase をする場合、一旦リモートリポジトリのmasterを取り込んだ上で、手元のmasterに積み上がっていたコミットを積み直します。

merge や rebase を取り消したくなったとき

ちなみに、merge や rebase が仮にうまく完了したあとでそれらを取り消したくなった場合、リモートリポジトリに push していなければ*3git reset --hard ORIG_HEADでコマンドを打つ前の状態に戻れます。

*1:共同作業しているブランチでrebaseしてpush

*2:force push しなければ失敗する

*3:リモートリポジトリに push しててもできなくはないけど歴史の辻褄が合わなくなる