spread の Operator Overloading

Groovy では List は * 演算子で 1つ外側の List の要素に展開できる。

groovy:000> [1,2,*[3,4,5]]
===> [1, 2, 3, 4, 5]

Groovy の演算子はメソッドで定義できるが http://groovy.codehaus.org/Operator+Overloading に * は載っていない。*1

groovy:000> [*new Object()]
ERROR java.lang.IllegalArgumentException:
cannot spread the type java.lang.Object with value java.lang.Object@5140709
        at groovysh_evaluate.run (groovysh_evaluate:2)

groovysh でたずねるとメッセージがでるのでソースを検索してみる。


ScriptBytecodeAdapter.despreadList の該当箇所

Object value = spreads[spreadPos];
if (value == null) {
    ret.add(null);
} else if (value instanceof List) {
    ret.addAll((List) value);
} else if (value.getClass().isArray()) {
    ret.addAll(DefaultTypeTransformation.primitiveArrayToList(value));
} else {
    throw new IllegalArgumentException("cannot spread the type " + value.getClass().getName() + " with value " + value);
}

展開演算子は null か List か 配列の場合に適用される。

タプル再び

Parser Combinator でタプルを定義したが元々 Groovy にタプルはあるみたいだ。
Groovyソースコード斜め読み(その1)「Groovyにもタプルがあった 」 - No Programming, No Life
@TupleConstructor はタプル風に呼び出せるコンストラクタってことだ。


Map 風もよいがタプル風に呼びたい場合もあるので再び定義する。

@groovy.transform.TupleConstructor(excludes="pair")
class Pos {
  int x
  int y
  @Delegate List pair = [x, y]
}


今回は多重代入だけでない。*2

@Newify([Pos])
def eval() {
  assert [1,2,3,4] == [*Pos(1,2), *Pos(3,4)]
  assert [Pos(1,2), Pos(3,4)] == [Pos(1,2), Pos(3,4)].collect { it }
  assert [Pos(2,1), Pos(4,3)] == [Pos(1,2), Pos(3,4)].collect { x, y -> new Pos(y, x) }
  assert [[1,1,1], [1,2,3]] == [Pos(1,1), Pos(1,2), Pos(1,3)].transpose()
}


でも扱いが List なので println では toString を呼び出してくれない。
InvokerHelper

public static String toString(Object arguments) {
    if (arguments instanceof Object[])
        return toArrayString((Object[]) arguments);
    if (arguments instanceof Collection)
        return toListString((Collection) arguments);
    if (arguments instanceof Map)
        return toMapString((Map) arguments);
    return format(arguments, false);
}

*1:Groovy イン・アクション p.54 には他に rightShiftUnsigned や as が載っている

*2:Closure は別クラスになるので @Newify が効かないのか?