月: 2020年9月

移転したコーヒー屋に行ったらデルモンテ工場跡地があった

好きでよく通っていたChromatic Coffeeというコーヒー屋が移転していた。この移転の話もこれはこれでちょっとややこしく、変な話なのだが、この記事の本筋とは関係ないのでひとまずおくとして、移転したのだ。

あたらしい場所はサンノゼのCalTrain駅からやや南にいったところ、正直あんまり足を踏み入れたことのない場所だ。治安がわるいとかではまったくないだろうが、shelter-in-place(自宅待機命令)の関係で営業しているのかもよくわからない。コーヒー豆は通販できるのでまったく営業していないわけじゃないだろうが、よくわからん。

というわけで行ってみた。8月頭のことなのでちょっと前のことだけど。

行ってみたらコーヒー屋が入居していたのは工場跡地を改装したようなノコギリ状の建物だった。周囲は大きなアパートが多い。もともと工場があったエリアの再開発地域なのだろう。ほかの入居店舗は自転車屋や氷屋、ビアバーなど。コーヒー屋としては営業はしていて注文もできるが、店内での飲食はもちろんできない。豆のローストはこの場所でやっているようだ。そこでコーヒー豆を一袋買い、ついでに妻と飲み物を一杯ずつ頼んだのだが、さてどうしたものか。駐車場の日陰で立ちのみをしている人もいるけれど、とおもって携帯電話の地図を見てみるとすぐ近くに公園があるっぽいので、そこにいってみることにしたのだ。

さてそのDel Monte公園という公園についてみたのだけど、道のむこうに見覚えのあるロゴがある。思わず二度見したが間違いなくデルモンテだ。デルモンテっていえばなんだろう。ケチャップか、トマトジュース? あれ。あのデルモンテのロゴだ。なんか貯水タンクみたいなやつにでかでかと描いてある。

IMG_20200802_155540

かなり近寄って撮った写真なので角度の関係でロゴがとても分かりづらいけど、よーくみるとおなじみのデルモンテのロゴが見て取れると思う。

デルモンテは日本だとキッコーマン傘下のブランドだが、もとはカリフォルニアの会社だ。もともとモントレー(シリコンバレーから南に行ったところにある街。今は水族館で有名)の近くに、かつてデルモンテホテルというリゾートホテルがあった。このホテルに卸す食料品を扱うということで(当時の)有名リゾートホテルの名前をつけたブランドがデルモンテなんだそうだ。そういう事情があるから、モントレーとサンノゼはそれなりに離れているとはいえ、関連する施設があってもおかしくはない。おかしくはないが気にはなる。

コーヒー片手に周辺を散策したら、史跡を紹介するパネルが道端に設置されていた。それによると、たしかにここはもともとはデルモンテの缶詰工場でフルーツ缶詰とかを作っていたものらしい。人口の多いサンノゼ近郊であり、線路の近くという地の利もあったため、けっこう大規模な工場があったのだそうだ。はじまったのは1893年(19世紀!)で、それが1999年まで工場として残っていたというから、けっこう最近だ。シリコンバレーともてはやされてドットコムバブルで湧いていたころ、いっぽうこのへんには缶詰工場があったりしていたのだ。

もちろん「けっこう最近」といっても20年以上前のことである。工場の跡地はやがて再開発され、いまではアパートになったり公園になったりしている。アパートになっているが、いわれてみれば工場跡の面影が残る構造だ。妙に広々とした入り口の道、意味もなく立ち並ぶコンクリの柱や建物の感じ。そしてなぞのデルモンテのロゴ入りタンク。

IMG_20200802_161521

公園もデルモンテの名を残しているだけじゃなくて、路肩の構造物が果物のかたちをしていたりして名残りがある。

コーヒー屋を追っかけに来ただけだったのだけど、なかなかおもしろいものを見た。近隣住民のみなさんもいっぺん行ってみると面白いと思う。

zig言語でtomlパーサを書いてみた

数ヶ月前にzig言語というやつの存在を知って、これはちょっと面白そうだなと思ったので、勉強がてらなにかやってみよう、と思っていた。ある日、tomlのパーサはどうだろうかと思い立ってしばらくやっていて(Ghost of Tsushima で作業が中断したりしつつ)、まあまあ出来上がってきたと思うので、現在のところのソースコードをgithubに置いておいた(https://github.com/jmuk/zig-toml)。

というわけで、zig言語をちょっと書いてみた感想を残しておく。なお、利用したのはzig 0.6.0なので、今後いろいろ変わってくる可能性もあることは強調しておきたい。

zig言語のよいところ・興味深いところ

型の扱いがzigでは興味深いところだった。zigはかなりいろんなところでcomptimeというマーカをつけてコンパイル時にコンパイラが事前処理をするようなことができる。これでたとえば、C言語であればマクロ展開させるようなコードも自然に書けるしわかりやすい。

さらに型もコンパイル時の値として扱うことができ、型によって処理を変えるような関数もかんたんに書ける。さらには、型を受け取って型を返す関数によってテンプレート・総称型を実現している。たとえば、

fn List(comptime T: type) type {
    return struct {
        items: []T,
        len: usize,
    };
}

のように書ける。これはちょっとかっこいい。型レベルの情報(たとえば構造体のフィールド名など)はコンパイル時には展開できて、ちょっとしたリフレクション処理のようなこともできる。リフレクションはコンパイル時にしか発生しないので、実行コードはおかしな遅さにはならない。

また、エラーになりうる型、オプショナルな型などはいくつか特別扱いしている点も面白い。null安全性なども担保されているし、Go言語などと違い、エラーの場合にはエラーだけが返るようになっていてわかりやすいし、エラーが値でありながらtry構文などにより帯域脱出っぽいようにも書けるのもよさそう。

zigには(たぶんGoの影響で)defer文があるのだけど、errdeferという「エラーのときだけ実行される」処理というのが書けるのはかなり素晴らしいと思うというか、正直Goにもこれがほしい。これもエラーが特別な値でありエラーになりうる型を特別視しているところとうまく結びついている。

困ったところ、微妙なところ

メモリ管理はやっぱり大変。zigはGCのない手動メモリ管理言語であり、Rustのようなオーナーシップのようなものもない。C言語と同じでメモリ管理は手動で頑張る必要がある。どこでメモリがアロケートされ、誰がオーナーで、どの操作でオーナーシップが移りうるのか、といったことは言語レベルでいっさいサポートがない。こういう言語で書くのは久しぶりで、面倒さはやっぱりある(defer / errdefer でかなり軽減されているはずだが、それでも)。

メモリ管理については、zigはアロケータというオブジェクトが標準で定義されていて、好きなアロケータを扱えるようになっている。これはいい面もある。ある種のオブジェクトをひとまとめのアロケータで管理してまるごと破棄したり、その範囲を越えたアロケーションを禁止したりできるし、たとえば、既存のアロケータをラップしたリーク検知アロケータも標準から提供されていて、これを使ってテストコードを書けばコードのメモリリークはかなり容易に検出ができる(ただ、どこでリークしたかという情報までは取れないので修正の厄介さはそれほど軽減されていない気がするが……)。

一方これは、どのアロケータがどのオブジェクトのデータを割り当てたのかということがまったく自明でないことを意味する。たとえばツリーのようなデータ構造が不要になったので破棄したくなったとして、再帰的にノードをたどっていって順次廃棄していくというコードを書くだろう。でも、ノードによって異なるアロケータから割り当てられていたということも原理的にはありうるから、ノードごとにただしいアロケータに対して廃棄するような再帰処理を正しく書くのは、わりと自明ではない。

実際、標準のデータ構造でも、データ構造からアロケータへのポインタを持っていることが多い。リソース開放用のメソッドは deinit と呼ばれる関数で行われるが、このメソッドはパラメータを取らず、自分自身が参照するアロケータにリソースの開放を依頼するかたちになっている。私もこの構造に従ったし、それはそれで便利なんだが、なんだかありとあらゆるデータ構造がアロケータへの参照を持っているというのはいいことなんだろうかという疑問がないでもない。もう少しうまい仕組みはないものか。

defer / errdefer については自分がGo言語にひっぱられすぎている面もあるが、ちょっと戸惑ったところもあった。zigのほうがわかりやすいというか直感的ではあり、defer / errdefer はそのスコープを抜ける時にしか発動しない。その一方、

while (i < input.len) : (i+=1) {
  var v = Value{.Table = Table.init(self.allocator)};
  errdefer v.deinit();
  ....
  if (xxxx) {
    break;
  }
  results.append(v);
  if (yyyy) {
    return i+1;
  }
}
return ParseError.FailedToParse;

のように書いた時、このコードがエラーを返してもvが開放されることはないので困ったということもあった。関数全体としてはエラーだが、errdeferの設定したスコープではエラー終了していない(正常にbreakしている)ためにerrdeferは実行されない。breakのかわりにその場でreturnでエラーを返すとerrdeferが実行される。ちょっとわかりづらいと思った(zigに慣れていないだけかもしれない)。

ほかにも、標準の文字列リテラルは[]u8でしかないというのはけっこう戸惑いがあった。ちょうどいい文字列型のようなものは存在しないし、文字列リテラルからかんたんに利用する方法もない。std.ArrayList(u8)がわりと使えるが、ArrayListは対象の文字列をつねに所有しているので作るなら毎回コピーするしかない。使いやすいユーティリティ関数も少ないし、もうすこしできの良い文字列型がほしい。

また、先述したように総称型が「型を受け取り型を返すコンパイル時関数」なのはかっこいいんだけど、これは裏を返すと、ある型に対して「これはArrayListの型である」かどうか、というチェックは書けない。ArrayList自体はコンパイル時関数であって、いま手元にある型Tが、どのコンパイル時関数から生成されたものなのかなんてことはわからない。tomlパーサのようなものを書くとき、呼び出し側が指定したデータ型の構造に応じてパース結果を埋めていくみたいなことをしたいわけだけど、そういう理由により、ライブラリの定義する型をうまくあてはめるのはかんたんではないという印象を持った。

もうひとつ、エラー型が単純なenumなのは正直納得がいかない。tagged unionもありにしてほしい。パースエラーが発生した時、エラーの発生箇所を埋め込みたいんだけど、できない。パーサーオブジェクトに「最後のエラーの箇所」みたいなプロパティを与えるみたいな方法がありうるだろうけれど、どうしてもアドホックになってしまう。まあtagged unionにした場合、エラーオブジェクトをアロケートしている途中で発生するエラーみたいなややこしい問題がありうるだろうから、この設計方針はわからないでもないのだが、しかし不便じゃないか?

けっきょく、面倒なので ParseError.FailedToParse だけを返すコードになっていて、どこでエラーになったかは検出する方法がないようにした。そのほうが実装が楽だから……なんだけど、テストが失敗したときなどはわりとつらい目にあってしまった。

tomlについて

tomlは(主にCargo.tomlで)ちょっとだけ書いたことがあるくらいで仕様のことはあんまり詳しくなかったので、パーサを書くのはtomlの詳細についてもいい勉強にもなった。人間にとってわかりやすい仕様だしあんまり長くなく、複雑度が低くて良いというイメージであったが、人間にとってわかりやすい仕様は、パーサを書きやすい仕様ではないという学びもあった。

とくに面倒なのは重複するキーの禁止と、[]で設定するテーブルとインラインテーブル・インラインアレイで設定されるものの再定義が禁止されるという仕様。単純にキーに対してオブジェクトをトラバースしていくだけなら実装が単純なんだけど、このパターンは上書きになってだめ、このパターンはむしろ許される、みたいなルールがいくつもあって、けっこうわからない。仕様に書いてある例をテストケースに移して確認することにしたけど、当初の実装方針だとぜんぜんだめなパターンもけっこうあって難しかった。