その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

C言語のジェネリクスサポート

全く知らなかったのだけれど,C11の新機能として_Genericという組み込み関数が提供されていた.
Generic selection - cppreference.com


jameshfisher.com

というのをこのブログを見て気づいたんですが,

#include <stdio.h>
int main() {
  char* x = "foo";
  printf("Type of x is: %s\n", _Generic(x, char*: "string", int: "int"));
  return 0;
}

これは完全に動くコード (最初よく文章を読んでなくて,大方擬似コードだろうと侮っていたら本当に動いて驚いた).
だいたい分かると思うんですが,上記のコードは char*の引数が_Genericに渡されるとstringという文字列を出力し,intの引数が与えられるとintという文字列が出力されるという挙動をします.

しかしこれが「リッチなgenericsか」というとそうではなくて,_Genericの引数に延々と型とそれに対応するvalueを書いていかなければならないので,どちらかと言うとType Assertionに近いイメージ.JavagenericsC++のTemplateのようにコンパイル時に良い感じに自動解決してくれるような類のものではない.惜しいですね.とはいえ便利ではある.

問題としてはC11なんてモダンなものを使う環境ってあるの……? という部分であるが……

Code sampleとしては以下がわかりやすかった: http://www.robertgamble.net/2012/01/c11-generic-selections.html

CircleCIでgradleのテストを走らせているとプロセスが突然死するんですけどって時

CircleCI上でgradleでテストを走らせていると,プロセスが突然死してテストがコケるという事態に見舞われていました.
運が悪いと10連続でfailしたりするなどかなり厳しい状況だったのでCircleCI Supportに泣きついたところ,

  • 症状としてはOOMで死んでるっぽい (なぜかOOM debug logは出ていなかった)
  • gradleは環境変数 JVM_OPTS を尊重しない
  • 一方で _JAVA_OPTIONS 環境変数は優先するので,制限をかけたいときはこれを使うと良い

という術を教えてもらったので,それに従ったところテストが完全に安定しました.
diffとしてはたったのこれだけ!

@@ -7,7 +7,7 @@ 
     working_directory: ~/repo

     environment:
-      JVM_OPTS: "-Xms2048m -Xmx2048m"
+      _JAVA_OPTIONS: "-Xms2048m -Xmx2048m"
       TERM: dumb

     steps:

事象としてはgradleは JVM_OPTS を尊重しないため,JVM_OPTS にいくらヒープ容量の制限を書いてもそれが反映されず,結果的にgradleがメモリを無尽蔵のバカ食いして死んでしまったということのようです.
_JAVA_OPTIONSJVMに直接的にパラメータを食わせることができるので,それを用いるとgradleにもヒープ容量の制限を強く課すことができるようです.

本件についてはCircleCIのこのブログが詳しくて良いです: How to Handle Java OOM Errors - CircleCI
しかし効く環境変数と効かない環境変数があるのはなかなか難しいですね……

arnoldというJavaのbuilderライブラリを書いた

今時builderライブラリも無いだろという感じですが,書いたのです.

github.com

Motivationにも書いているのですが,普通に作ったJavaのbuilderは不完全なインスタンスを作成できる可能性を孕んでいます.注意深くコードを書けば問題ないかもしれませんが,えてして我々 (主語が大きい) はミスを犯しがちです.そしてNPEがやってきて……我々はしぬ.

というわけで今回作ったのがarnoldです.
端的にいうと「必要なすべての要素を取りこぼさないbuilder」であり,「そのジェネレータ (annotation processor)」でもあります.Basic usageにあるように,

import net.moznion.arnold.annotation.ArnoldBuilder;
import net.moznion.arnold.annotation.Required;

@ArnoldBuilder
public class Example {
    @Required
    private String foo;
    @Required
    private int bar;
    @Required
    private double buz;
}

というようにclassに @ArnoldBuilder を付与することでinstantiate対象のclassであることを示し,builderでbuildしたいフィールドについては @Required を付与することで必要なフィールドであることを示すことができます.そしてプロジェクトをビルド *1 するとAnnotation Processorが走り,builder classが自動生成されます.
自動生成されたbuilderはこのように使うことができます.

Example example = new ExampleBuilder().foo("foo")
                                      .bar(42)
                                      .buz(2.71828)
                                      .build();

このBuilderは分解して書いてみると

ExampleBuilder exampleBuilder = new ExampleBuilder();
ExampleBuilder1 exampleBuilder1 = exampleBuilder.foo("foo");
ExampleBuilder2 exampleBuilder2 = exampleBuilder1.bar(42);
ExampleBuilder3 exampleBuilder3 = exampleBuilder2.buz(2.71828);
Example example = exampleBuilder3.build();

というふうになっており,つまり

  • ExampleBuilderfoo を受け取って ExampleBuilder1 を返すsetterを提供する
  • ExampleBuilder1bar を受け取って ExampleBuilder2 を返すsetterを提供する
  • ExampleBuilder2buz を受け取って ExampleBuilder3 を返すsetterを提供する
  • ExampleBuilder2build() を提供する

というようなコードをAnnotation Processorが一式生成するという挙動となっています.なかなか富豪的ですね.

このように,1つのbuilder (もはやbuilderではない気もしますが) が高々1つの要素を受け取り次のbuilderを返却し,終端のbuilderが build() して所望のclassのインスタンスをinstanciateする,という仕組みにすることで

  • 「build途中の不完全な状態のインスタンス」は何らかのbuilder classになるので,build対象のインスタンスをinstantiateすることは出来ない
  • 必須なフィールドがすべて揃ったらその時点でbuilderはbuild()を提供し,ようやくbuild対象のインスタンスをinstantiate出来るようになる

という安全な挙動を実現しています.


今後の課題としてはprivate classのinstantiateができないというものがあり,これはなんとかしたい *2


それはそうと今回Javaのコード生成には square/javapoet を使ってみたんですが,使いやすく,なおかつパワフルで良いですね.
現場からは以上です.ぜひご利用下さい.

*1:紛らわしいですね

*2:これなんとかなるんですかね?

YAPC::Okinawa 2018 Onnasonに行きつつ喋りました

yapcjapan.org

行ってきて,そして喋りました.
スライドは以下にあります.

speakerdeck.com

Perlコードに別の言語のコードを埋め込んで動かしてしまう技術であるところのInlineモジュールの話です.今回のYAPCのテーマは「万国津梁」とのことだったので「じゃあPerlと他の言語をつなげるInlineモジュールの話でもすればよかろう」と短絡的に題材を選択してしまったわけですが,そのままではなかなか「引っ掛かり」が無い話になってしまったため (出オチみたいな感じになった),色々風呂敷を広げてみました.それはそうとしてnumpyマジで速いですね.それだけ覚えて帰って下さい.
「グルー言語」の部分については碌々調べずにスライドのあるようなことを喋ったわけですが,恐らくこのような点はあるのだろうなと思っています.
かつて言語側で頑張らなければならなかったコンポーネント間の「糊付け」の責務が,HTTPやTCPのような「プロトコル」のレイヤに寄ってきている,あるいは別の見方をするとprotobuf等のIDLやJSONなどのデータ交換のための表現が発達したことによって「言語」のレイヤから更にメタな「表現」のレイヤにコンポーネントを協調させる責務が寄ってきた (寄ってきたというよりも,「表現」がその責務を負えるだけの進化を遂げたと言う方が適切かもしれません) というのがあるのかなと思います.これはいずれか片方にだけ寄っているという意味ではなく,複合的な要因によるものでしょう.もちろんスライドにも書きましたがLLVMのようなコードを変換するような潮流も,パラダイムを変えていった一因だと考えています.
とは言え「グルー言語」というエッセンスが無価値なものになったかと言うとそうではなく,むしろ選択肢が広がったという見方をするのが自然だと思っているところです.
懇親会ではid:nkgt_chkonkさんが,「例えばプリで計算した結果とかをどこかストレージに入れておいて,それをアプリ側で引いてくるっていうシチュエーションでは,ストレージもグルーのひとつだよね」とおっしゃっていて,たしかにそのとおりだなあと思いました.グルーはあまねく遍在している.これ,僕が言ったことになりませんかね?


それはさておき,今回のYAPCも興味深い話がたくさんあり,特に新屋さんのトークid:akiymさんのトークは大変おもしろく聞かせて頂きました.
新屋さんのトークは平易な言葉を用いて構文解析における曖昧性の解説をされていて大変勉強になりました.4年前に知りたかった…… *1.語り口としてもちゃんと笑わせてくる部分を作って確実に聴衆を引き込んでいくさまは流石ですね.
id:akiymさんのトークperlの新しくて便利なモジュール紹介100連発という感じで,常に新しい情報を追うというそのアンテナの高さと,積極的にそれを取り込んでいく姿勢には学ぶべきものが多いと思いました.ところで懇親会で酔っ払ってて訊けなかったんですけど,おもにどこで情報集めてるんですか?


というわけで,今回のYAPCはそんな感じでした.運営の皆さん,お疲れ様でした.
次回は東京に戻ってくるということで楽しみですね!

*1:色々なことが当時あった

AWS Lambdaでnodeを動かす時にnode_modulesをどうするか

向かうべき道は色々考えられるが,実際に試してみたのは以下.

1. node_modulesをzipに含めてアップロードする
2. browserifyを使って1つのjsファイルにバンドルする

node_modulesをzipに含めてアップロードする

ローカルでnpm install (or yarn install) してこさえたnode_modulesディレクトリをzipにアーカイブしてそれをLambdaにアップロードするという方法.
zipファイルを作るのは地味に面倒に思えますが,Apexを使うとかなり楽にzipファイルの作成とアップロードができます.しかし得てしてzipファイルの容量が大きくなりがちなのでS3経由でデプロイするなどの方法を採る必要があるかもしれません.
また,ネイティブ拡張を利用しているライブラリが含まれている場合だとLambdaの実行環境 (アーキテクチャ) と同一の環境上で npm insall してnode_modulesを作る必要があるでしょう.
可能な限りnode_modulesの容量を小さくするために npm/yarn install --production でdevDependenciesを外すというのも良い考えです.

pros
  • 手っ取り早い・わかりやすい
cons

browserifyを使って1つのjsファイルにバンドルする

ようこそ地獄へ.
node_modulesをアップロードする方法は容量が大きく,スマートではない……おれはもっと格好良い,小洒落た方法でやりたいんだ……ということでbrowserifyを使って1つのjsファイルに依存をバンドルし,それをアップロードするという方法が思いつくでしょう.

とりあえずLambdaで動くbundled jsを構築するには以下のようにコマンドを実行してやる必要があります.

$ browserify --node --standalone 'your-app-name' index.js -o bundle.js

--node--standalone 'your-app-name' を指定して実行すると,運が良ければ動くjsが生成されます.運が悪いと動かない.動かない時はどうするか? おそらくおとなしく諦めたほうが良いでしょう……人生は短い…… (不毛すぎる)
もちろんネイティブ拡張を利用しているライブラリが含まれている場合だと動かないので,そういうときはnode_modulesをzipにバンドルする手法を採用する必要があります.
あるいはbrowserifyの代わりにwebpackを使うという方法も考えられる.僕はやっていません.

pros
  • node_modulesをバンドルする方法と比較して容量が小さくなる
  • おしゃれ
cons
  • ハマりがち
  • ネイティブ拡張を利用していると使えない

結論

おとなしくnode_modulesをバンドルする方法のほうが楽なのではないか.
あるいは外部モジュールに依存せずにLambda Functionを書くという強い気持ちを持つ (つらい).

EOLなUbuntuを使い続けるとどうなるのか

TL;DR

apt-get関連のコマンドが死ぬ.

例: Ubuntu 16.10の場合

Release end of life | Ubuntu

f:id:moznion:20180126161950p:plain

この図からもわかるようにUbuntu 16.10は現時点でEOLです.使ってはいけません.
しかし生きているとうっかりEOLなバージョンが残っていることもあるでしょう.あったのです……

で,EOLを迎えているUbuntuを使い続けるとどうなるか.特筆すべきは apt-get update が死ぬという点でしょう.

$ sudo apt-get update
...
Err:6 http://security.ubuntu.com/ubuntu yakkety-security/universe Sources
  404  Not Found [IP: 91.189.88.152 80]
...
Err:14 http://archive.ubuntu.com/ubuntu yakkety/universe Sources
  404  Not Found [IP: 91.189.88.149 80]
...
Err:27 http://archive.ubuntu.com/ubuntu yakkety-updates/universe Sources
  404  Not Found [IP: 91.189.88.149 80]
...
Err:37 http://archive.ubuntu.com/ubuntu yakkety-backports/multiverse amd64 Packages
  404  Not Found [IP: 91.189.88.149 80]
...
Reading package lists...
W: The repository 'http://security.ubuntu.com/ubuntu yakkety-security Release' does not have a Release file.
W: The repository 'http://archive.ubuntu.com/ubuntu yakkety Release' does not have a Release file.
W: The repository 'http://archive.ubuntu.com/ubuntu yakkety-updates Release' does not have a Release file.
W: The repository 'http://archive.ubuntu.com/ubuntu yakkety-backports Release' does not have a Release file.
E: Failed to fetch http://security.ubuntu.com/ubuntu/dists/yakkety-security/universe/source/Sources  404  Not Found [IP: 91.189.88.152 80]
E: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/yakkety/universe/source/Sources  404  Not Found [IP: 91.189.88.149 80]
E: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/yakkety-updates/universe/source/Sources  404  Not Found [IP: 91.189.88.149 80]
E: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/yakkety-backports/multiverse/binary-amd64/Packages  404  Not Found [IP: 91.189.88.149 80]
E: Some index files failed to download. They have been ignored, or old ones used instead.
$ echo $?
100

ワーオ,repositoryが404になっていますね.それが起因してコマンドが100でexitしています.

curlを打ってみましょう.

$ curl -I http://security.ubuntu.com/ubuntu/yakkety-security/universe
HTTP/1.1 404 Not Found

無慈悲にdistのパッケージがremoveされてる!! マジかよ.

解決するには
  • Ubuntuのバージョンを上げる
小話

インスタンス起動時にapt-get updateを走らせるような設定にしていたため,毎回起動時に失敗してインスタンスごと死んでしまい,そしてそれを埋めるために更に新しいインスタンスが自動で立ちあがって死に……という無間地獄が行われていてすごい状況だった.

結論

apt-get系のコマンドがしくじるだけでなく様々な悪いことが起きる.
EOLなUbuntuを使うのをやめよ.

GitHub releasesのフィードを購読する

生きているとOSSのライブラリを使ったり,OSSのソフトウェアを使用することになるでしょう.
そうなってくると内部実装や変更点を逐一知りたくなるというのが人情というものです.
GitHubでコードが公開されているのであれば,「リポジトリをwatchする」というのは有効な方法に思えますが,しかしwatchしているリポジトリが増えてくると現状のGitHubのタイムラインは即座に崩壊し,容易に取りこぼしが生じてしまうでしょう.これはGitHubのタイムラインの問題の一つだと思っていて,なんとかなって欲しい点はあります (例えばタイムラインを分割できるとか……).

というわけでどうするか.もちろんAtomフィードです.

https://github.com///releases.atom

と指定してやると当該repositoryのGitHub releasesをAtomフィードで取得することが可能となります.これで(必ずreleaseを切るプロジェクトであれば)変更をトラックできるようになって便利です.場合によっては https://github.com///tags.atom を利用するのでも良いかもしれませんね.

というわけでreleasesフィードをSlackのRSSリーダーAppを介してチャンネルに流すようにしたので,関係するメンバーに周知できるようになって便利になりました.良かった良かった.