kagamihogeの日記

kagamihogeの日記です。

アトラス作品ファンのオフ会 真・眼鏡祭Ⅱ(2017/07/15)に行ってきた

f:id:kagamihoge:20170723143501j:plain

7/15 真・眼鏡祭Ⅱ - TwiPla(開催済み)に行ってきました。これはその個人的なイベントレポートになります。当日の様子についてはtwitterハッシュタグ #眼鏡祭0715から追えます。

当日は天候には恵まれましたが、朝から既にかなりの暑さでした。会場のキリストンカフェ東京 (Christon cafe 東京)では、乾杯の前にお水の用意をして頂いたり、有志提供の塩飴・塩タブレットが配られたりもしました。人数は今回も安定して200人オーバーで密度も中々で、色々な意味で今回は暑さが印象に残りました。

眼鏡祭はコスプレ可能な大規模オフ会

眼鏡祭とは、ペルソナを中心としたアトラスファンによる大規模なオフ会です。眼鏡の由来はペルソナ4のオフ会から出発したためで、同作品のファンが中核に居ますが、現在まで少しずつ形を変えつつ今は広くアトラスファンが集う場となっています。人数の多さやコスプレが目立ちますが、実情は単なるオフ会です。つまり、何か催事がある訳では無く、ゲーム・アニメ・マンガの話題で盛り上がる場でしかありません。

眼鏡祭はコスプレが可能で、おおむね6~7割は何らかのコスプレをします。上記引用ツイートは毎回恒例のコスプレ参加者に集まってもらっての集合写真です。毎回少しずつ傾向が違うのですが、今回はかなりバラけた感があります。とはいえペルソナ5がやはり強く、佐倉双葉が沢山居たのが目を引きました。P5のメイン・サブキャラクターの、制服・私服の夏・冬のバリエーションで個々のレイヤーさんの個性が出るのが面白い所です。もちろん、いままでの作品の根強いファンによるコスプレも相変わらず素晴らしく、この空間にアトラス作品という軸のコスプレが集まる光景は、何度見ても圧倒的です。

個人的には、レイヤーさんとの距離が極めて近いので、写真を撮ったり色々話を聞きやすい点も眼鏡祭のポイントだと考えています。

コスプレ中心のイベントや撮影会では無いので注意事項色々

まず、200人が集まる大規模なイベントなのでどうしてもトラブルや不満とは無縁でいられません。いくつか注意事項があり、これを守れない方・破ってしまった方には、運営から何らかの対処が行われます。コスプレ関連でいえば、撮影の際には必ず許可を得ること、また、SNS等へのアップロードに関しても許可を得ること、とされています。また、更衣室もお店のフロアや部屋を暗幕で区切っただけなため、照明や荷物置き場の設備はコスイベと比較すれば十分とは言えません。

f:id:kagamihoge:20170723142037j:plain

とはいえ、これらルールは大人同士が楽しくオフ会を過ごすためのマナー程度のものです。基本的にはほとんど意識する程のものではないです。

初めてひとりで参加しても楽しめるかどうか

この辺りはステマめいてはいますが実際の声を拾うのが良いでしょう。

とはいえ、正直、200人のオフ会にひとりで来るのは相当な度胸があるといって良いでしょう。この辺りは運営側の知恵の凝らしどころで、毎回マイナーチェンジを繰り返しつつ色々なやり方を試みています。

参加者にはそれぞれ上記のような名札が配布されます。また、SNSのIDを印刷したシールも配られるので、これを交換した後ほどフォローしあう等に用います。交換の際、一言メモを残すのが有効で、相手が何のコスプレをしていたか、何のゲームを薦められたか、などを記しておくと後で思い出しやすくなります。

毎回記念品としてノベルティも配られるのですが、これらを交換しあったりするのも一つの手です。

とはいえ、最後の最後に頼れるのは自分次第としか言い様がありません。しかし幸か不幸か眼鏡祭は人で一杯であり、セクハラ一発退場とかを除けば、コミュニケーション上の多少の失敗は許されるというか目立たないのが実情です。また、大人で優しいコミュニティを大事にする人が多いので、あまり気にしない人も多く、見た目の派手さに反して敷居の低いオフ会ではないか、と思います。なんだかんだで毎回1~2割は新規勢でもあります。

みんなで作られる眼鏡祭

眼鏡祭は、イベント会社を使っているわけではなく、運営スタッフは全員有志です。比較的わかりやすいものでいうと、会場の開始前後のレイアウト変更は有志による力仕事です。また、何かとトラブルになりやすい更衣室の管理も同様です。

加えて、参加告知用のイラストを描いて頂ける方もいて、こちらはRTなり何なりで目にした方も多いでしょう。

勿論、これ以外にも仕事は多岐に渡ります。各テーブルの進行・案内を務めるコミュニティリーダー、会場案内、受付、名簿管理に参加費管理あたりは当日我々の目に触れるところです。これ以外にも、一般の参加者の見えないところで、企画を出したり進捗を管理したり、様々な事務作業が多数待ち構えていることは簡単に想像できます。

眼鏡祭は参加者皆で支えるもの、というのは実際に行ってみれば言われずとも何となく伝わるし、そうであれば自分も何か出来ることはないか、となるのも人情というものです。私は運営スタッフでは無いので確たることは言えませんが、お手伝い希望に関しては主催のマソー(@muscle_bomber)さん | Twitterに気軽に聞いてみるのが良いと思います。

以降のイベント

眼鏡祭はある意味ポータルであり、これを基点に色々なイベントに顔を出して友好を深める切欠に使うのも一つの手段です。単なる飲み会の8/12 真・眼鏡祭Ⅱ おかわり会 二杯目 - TwiPla、マソーさんのお誕生日会も兼ねたイベントで8月26日(土) 夏の盆(ぽん)まつり - TwiPlaなど。8/2のPERSONA SUPER LIVE P-SOUND BOMB2017に行く人も多いかと思われます。

また、関西眼鏡祭の次回も決定したようです。東京に関してはまだ確定では無いようですが11月のようです*1

最後に

最後になりましたが、主催のマソーさんはじめ眼鏡祭運営スタッフの皆さん、同じテーブルとなった死神コミュの方々、このレポートを書くに当たり勝手ながら引用ツイートをさせて頂いた方々、当日自分が何していたかを知っている人はがんばったのは分かるけどコスプレとしては……まぁがんばっているのを生暖かく見守りつつ相手をしてくれた方々、大変楽しい時間を過ごせました。何かのイベントで会う機会があればコンゴトモヨロシク。

リンク

JEP 305: Pattern Matchingをテキトーに訳した

http://openjdk.java.net/jeps/305 をテキトーに訳した。

JEP 305: Pattern Matching

Author   Brian Goetz
Owner   Gavin Bierman
Created 2017/05/30 19:48
Updated 2017/06/16 16:25
Type    Feature
Status  Candidate
Component   specification/language
Scope   SE
Discussion  amber dash dev at openjdk dot java dot net
Priority    3
Reviewed by Mark Reinhold
Issue   8181287

Summary

Java言語をパターンマッチング(pattern matching)による機能強化を行います。初回のサポートは、switchステートメントmatches式による、型テスト(type-test)と定数パターン(constant patterns)になります。パターンの適用範囲と言語でパターンマッチングをサポートする要素の拡張は、以降の開発で行います。

Motivation

おおむね大半の言語は、ある式が特定の型や構造を持つことのテストと組みわせる一連のロジックがあり、その次に、以降の処理で使うために状態のコンポーネントを条件付きで展開します。たとえば、Javaではinstanceof-and-castイディオムが良く知られています。

if (obj instanceof Integer) {
    int intValue = ((Integer) obj).intValue();
    // use intValue
}

ここには三種類の処理があります。test(xはIntegerである)、conversion(objをintegerにキャスト)、destructuring(IntgerからintValueコンポーネントを抽出)。このパターンは単純でありJavaプログラマには馴染み深いものですが、最適とは言い難い理由がいくつかあります。まず冗長さで、型テストとキャストはやることが被っています(instanceofしたあと他に何をやるというのだろうか?)。キャストとdestructuringという突然現れるボイラープレートにより、それに続く重要なロジックが見辛くなります。しかし最も甚大なのは、ポイラープレートの繰り返しがプログラムにエラーを気付かれないまま紛れ込ませる可能性がある点です。

複数のターゲット型がありうる場合をテストする場合にこの問題は更に悪化します。上述の例をif...elseテストチェーンに書き加えます。

String formatted = "unknown";
if (obj instanceof Integer) {
    int i = (Integer) obj;
    formatted = String.format("int %d", i);
}
else if (obj instanceof Byte) {
    byte b = (Byte) obj;
    formatted = String.format("byte %d", b);
}
else if (obj instanceof Long) {
    long l = (Long) obj;
    formatted = String.format("long %d", l);
}
else if (obj instanceof Double) {
    double d = (Double) obj;
    formatted = String.format(“double %f", d);
}
else if (obj instanceof String) {
    String s = (String) obj;
    formatted = String.format("String %s", s);
}

上のコードは見慣れたものですが、多数のよろしくない要素が含まれています。既に述べたように、こうした繰り返しコードはプログラマを苛立たせます。また、ビジネスロジックはポイラープレートの中にたやすく埋もれてしまいます。しかしより重要なのは、このやり方はコーディングエラーを残したままにしてしまう点で、その理由は過剰な制御構造になっているためです。上のコードの意図は、if...elseチェーンの各箇所でformatted変数に何らかの値を代入することです。しかし、ここで実際に発生することをコンパイラで検証可能にする術がありません。もし、あるブロックが、実際上滅多に実行されないブロックだとして、formattedへの代入を忘れているとバグになります(blank localもしくはblank finalでformattedにしておけば少なくとも"definite assignment"分析には入れられますが*1、常に行われるわけではありません)。最後に、上のコードは最適化の余地が限られており、absent compiler heroics,*2 基底のプログラムはおおむねO(1)なのに、O(n)の複雑さを抱えています。

Description

アドホックな解決策に頼るより、Javaにもパターンマッチング(pattern matching)を入れる時が来たと我々は判断しています。パターンマッチングは1960sにさかのぼる事プログラミング言語でさまざまな異なるスタイルに適用されてきた技術で、SNOBOL4とAWKなどのテキスト指向言語、HaskellとMLなどの関数型言語、最近ではScalaC#などのオブジェクト指向言語にも拡張されています。

パターン(pattern)とはターゲットに適用可能な述語(predicate)の組み合わせです。述語はバインド変数(binding variables)と共に使用し、もし述語が適用される場合はターゲットから抽出されたものがバインド変数になります。バインディングパターンの形の一つは型テスト(type test)パターンで、以下のようなものです(matches演算子は概念的なものです)。

if (x matches Integer i) {
    // can use i here
}

Integer iというフレーズが型テストパターンです。iは新規の変数宣言で、宣言済みの変数ではありません。ターゲットはIntegerインスタンスかどうかテストされ、次に、Integerにキャストされてバインド変数iintコンポーネントがバインドされます。

先に触れたように、if...elseの連続は過度に制御構造を多用しているので望ましくありません。Javaには既にswitchというmulti-armed*3な等価テストの機構があります。しかしswitchは(今のところは)極めて限定的なものです。ごく少数の型、numbers, strings, enums、のみswitch可能で、さらに、定数に対する等価性しかテスト出来ません。とはいえこれら制限はおおむね歴史的経緯なだけであり、switchステートメントはパターンマッチングにパーフェクトに"マッチ"します*4。もしcaseラベルでパターンを指定可能になれば、switchで上述の例を以下のように書けるようになります。

String formatted;
switch (obj) {
    case Integer i: formatted = String.format("int %d", i); break;
    case Byte b:    formatted = String.format("byte %d", b); break;
    case Long l:    formatted = String.format("long %d", l); break;
    case Double d:  formatted = String.format(“double %f", d); break;
    case String s:  formatted = String.format("String %s", s); break
    default:        formatted = obj.toString();
}

これにより、正しく制御構造を使用するようになったため、コードの意図がかなり明瞭になります。つまり、"式objは以下の条件のうち少なくとも一つにマッチし、マッチした行を実行する"という意図を示しています。加えて、最適化可能性も良くなり、この例の場合はO(1)でディスパッチ可能となる可能性が高いです。

従来のcaseラベル(コンパイル時の定数、数値・Stringenum、との比較)は、両者がObject.equals()で等価の場合にターゲットが定数パターンにマッチするという点で、定数パターン(constant patterns)と言えます。なお、マッチした定数パターンは何もバインディングしません。

初回の機能拡張では、定数パターン、switchステートメントでのバインディングmatches式による型テストパターン、のサポートを目的とします。ガード(マッチするにはtrueにならなければならない補助的なboolean式、たとえばcase String s && !s.isEmpty()と、switch内のcontinueステートメント、の両方あるいは片方)をサポートする可能性もあります。

Future Work

型テストパターンとswitchのパターンは最初の一歩に過ぎませんが、それでも明らかに第一歩です。将来的な取り組みの対象となりうる領域(JEPのターゲットになる)には以下があります。

Deconstruction Patterns. クラスには単にデータを保持するものが多くあります。コンストラクタで生成し、その際にN個の引数を取り集約を生成しますが、基本的にはアクセサで一度に一つのコンポーネントをフェッチします。 型テスト-キャスト-バインドの操作と一つの型テストパターンを組み合わせられるので、型テスト-キャスト-複数抽出と一つのdeconstruction patternを組み合わせられます。いま、Nodeの型階層があり、そのサブタイプに、IntNode(単一のintを持つ)、AddNodeMulNode(二つのnodeを持つ)、NegNode(単一のnodeを持つ)があるとすると、Nodeに対するマッチをして特定のサブタイプにおける挙動をすべてワンステップに収められます。

int eval(Node n) {
    switch(n) {
        case IntNode(int i): return i;
        case NegNode(Node n): return -eval(n);
        case AddNode(Node left, Node right): return eval(left) + eval(right);
        case MulNode(Node left, Node right): return eval(left) * eval(right);
        default: throw new IllegalStateException(n);
    };
}

現状、上記のようなアドホックポリモーフィックな計算を表現するには、"Visitor"パターンを使います。パターンマッチングを使うことで、より透過的で単純な理解しやすいものになります。

Nested Patterns. 上の例で既にnested patternを使用しており、deconstruction patternsの"引数"である、Node nなどは、既にこれ自身がパターン(この場合は型テストパターン)です。いま、左側がゼロのIntNodeAddNodeとマッチさせたい場合、もう一段階ネストを追加します。

case AddNode(IntNode(0), Node right)

上の例では、deconstruction pattern(AddNode(...))で左側のコンポーネントが更に別のeconstruction pattern(IntNode(...))にマッチし、このパターンの内側では一つのコンポーネントが定数パターン0にマッチします。

Expression switch. switch statementは今のところステートメントですが、結果を処理して続行したい場合、いくつかのやり方から選ぶことになります。*5switchを式にも出来るようにすることでswitchステートメントで式のようなことを実現せざるを得ない歪みを矯正します。

Sealed types. switchのcaseが網羅的であると事前に分かっていることは有用です。つまり、基本的な状況下では決して実行されないdefaultを書く必要が無い、ということです。階層構造に網羅性を組み込めるのであればクライアントに対し有益な制約を示すことになり、コンパイラの網羅性分析の助けになります。

Alternatives

型テストパターン(deconstruction patternsでは無い)はifswitchステートメントもしくはtype switchflow typingによっても実現できます。パターンはこれら制御構造を汎用化するものです。

Dependencies

実装にはおそらくDynamic Constants in the JVMを使用します。(https://bugs.openjdk.java.net/browse/JDK-8177279).

*1:“definite assignment” analysis in this effortが原文。日本語での表現が良くわからんかったので、うまく訳に自信が無い。

*2:よくわからん

*3:日本語でなんていうか分からんけど、このすぐ下のswitch caseの例のように並列にcaseが並んでる様を「多腕」と表現しているのだろう

*4: perfect “match” for pattern matching.が原文で、意図的にmatchを二回出してると思われ、いわゆるダジャレの匂いがする。

*5:The switch statement is currently a statement, but very often when we are choosing between several alternatives, we want to populate a result and keep going.が原文。うまく訳せない

Spring BatchのCommandLineJobRunnerで任意の終了ステータスを返す

背景

たとえば、Spring BatchをCommandLineJobRunnerを使用するjavaコマンドで起動し、そのjavaプロセスの終了ステータスをシェルスクリプトで取得して何らかの条件分岐を行いたい、とする。基本的には、Spring Batchはその終了状態に応じて0,1,2を返すのでこれで十分なのだが、それ以外の任意の値を返したい場合がある。

ソースコードなど

環境

  • spring-batch 3.0.7.RELEASE

JVM終了時に呼ばれるSystemExiter

CommandLineJobRunnerはJVM呼び出し時の拡張ポイントとしてpresetSystemExiterを用意しているので、ここで自前のSystemExiterを設定できる。

import org.springframework.batch.core.launch.support.CommandLineJobRunner;
import org.springframework.batch.core.launch.support.SystemExiter;

...
    public static void main(String[] args) throws Exception {
        CommandLineJobRunner.presetSystemExiter(new SystemExiter() {
            public void exit(int status) {
                System.exit(status);
            }
        });
        CommandLineJobRunner.main(args);
    }

ちなみにEclipseで終了ステータスを確認するにはdebugビューで見れる。

f:id:kagamihoge:20170625183723j:plain

afterJobでExitStausのexitCodeを指定してExitCodeMapperで変換

上のコードは固定値だが、exitメソッドのstatusは基本的にはExitStatusの値が入ってくる。ただし、CommandLineJobRunnerデフォルト動作のSimpleJvmExitCodeMapperが0,1,2のいずれかにExitStatusの値を変換する。そのため、それ以外の値にしたければ自前のExitCodeMapperを設定してやる必要がある。

まず、適当な終了ステータスのExitStatusを返すJobExecutionListenerを作る。リスナ設定のXMLなどについては省略。

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;

public class JobListener implements JobExecutionListener {

    @Override
    public void beforeJob(JobExecution jobExecution) {
    }

    @Override
    public void afterJob(JobExecution jobExecution) {
        jobExecution.setExitStatus(new ExitStatus("114"));
    }

}

自前のExitCodeMapperを作る。ここでは、単にExitStatusのexitCode文字列をintに変換するだけ。上記のJobListener#afterJobでexitCodeに114の文字列を指定しているので、以下のメソッドはintの114を返す。

import org.springframework.batch.core.launch.support.SimpleJvmExitCodeMapper;

public class CustomExitCodeMapper extends SimpleJvmExitCodeMapper {
    @Override
    public int intValue(String exitCode) {        
        return Integer.parseInt(exitCode);
    }
}

次に、CommandLineJobRunnerにsetExitCodeMapperがあるので、これを使用してセッターインジェクションで、上で作成した自前のCustomExitCodeMapperを設定する。方法は色々あるが、たとえばXMLなら以下のようにする。

<bean id="exitCodeMapper" class="kagamihoge.springbatchexitcode.CustomExitCodeMapper" />

ここの仕組みについては、CommandLineJobRunnerは起動時に引数に与えられたxmlをスキャンし、それを自身にも適用している。なので、たとえばsetExitCodeMapperに対応するexitCodeMapperというidのbeanがあればそれをインジェクションする。

最後に、最初に作成したSystemExiterのところを引数の値をそのままSystem.exitの引数に渡すように変更する。

       CommandLineJobRunner.presetSystemExiter(new SystemExiter() {
            public void exit(int status) {
                System.exit(status);
            }
        });

これで、afterJobで何らかの条件分岐を行い、それを基に任意の終了ステータスを返すことが出来る。

参考URL