

Java のライブラリに用意されているオーバーロードされたメソッドを Scala で利用しようとしています。

[code]

    class X{
      public <E> void f(E... values){ System.out.println(1); }
      public void f(Object value){ System.out.println(2); }
    }
    
[/code]

しかし Scala ではパラメータの型に互換性がある場合、オーバーロードされたメソッドのどれを使って良いか解決することができずコンパイルエラーが発生します。

[code]

    val x = new X()
    x.f("foo")
    
[/code]

[code]

    error: ambiguous reference to overloaded definition,
    both method f in class X of type (value: Any)Unit
    and  method f in class X of type [E](values: E*)Unit
    match argument types (String)
    
[/code]

呼び出し時のパラメータ部分を `"foo":Object` や `"foo".asInstanceOf[Object]`
などにしても状況に変化はありません。回避策としてリフレクションを使用することはできます。

[code]

    classOf[X].getMethod("f", classOf[Object]).invoke(x, "foo")  // => 2
    
[/code]

根本的に、Scala で 2. 側のメソッドを明示的に呼び出すにはどうすれば良いでしょうか?

Java 11.0.2 / Scala 2.12.8


CC BY-SA 4.0
2019-05-03T15:32:42.437
54693
2019-06-14T07:01:30.550
2827
question
java scala
オーバーロードされたメソッドの曖昧さを Scala で明示的に解決するには?


おそらくですが、Scalaから`setDefault(Object value)`のメソッドを呼び出すことはできません。

dotty(Scala 3.x)では、何かしらの方法により、可能になるかもしれません(?)。  
<https://github.com/lampepfl/dotty/issues/5792>

とりあえず、妥協案が3つほど考えられます。  
(リフレクションで呼び出す方法は質問文に書かれていますので除きました)

**妥協案 1: 他のライブラリの使用を検討する**

  * <https://stackoverflow.com/questions/2315912/best-way-to-parse-command-line-parameters>
  * <https://stackoverflow.com/questions/8112879/scala-command-line-parser-with-subcommand-support>

**妥協案 2:`setDefault(E... value)`のメソッドを呼び出す**

こちらのメソッドであれば、以下のようなコードで呼び出すことができる気がします。

[code]

    arg.setDefault(Seq("foo"): _*)
    
[/code]

ただし、ヘルプで表示されるデフォルト値が、以下のような表示になるかと思います。  
(余計な`[]`が付いてしまう)

[code]

    (default: [foo])
    
[/code]

**妥協案 3: Javaでヘルパーclassを書き、Scalaで使用する**

argparse4jのScalaラッパーである[mdekstrand/argparse4s](https://github.com/argparse4j/argparse4j)で使用されている方法ですが、`src/main/java`配下に

`public static void setDefault(Argument arg, Object dft)`

というメソッドを持つヘルパーclassを定義しておき、このJavaのclassをScalaから使用します。

[`/src/main/java/net/elehack/argparse4s/ArgConfig.java`](https://github.com/mdekstrand/argparse4s/blob/a9178bbdb90888e706da62ac06ac3aaabf07d909/src/main/java/net/elehack/argparse4s/ArgConfig.java)

[code]

    package net.elehack.argparse4s;
    
    import net.sourceforge.argparse4j.inf.Argument;
    
    class ArgConfig {
        public static void setDefault(Argument arg, Object dft) {
            arg.setDefault(dft);
        }
    }
    
[/code]

※このScalaラッパーライブラリ自体は、2019年現在メンテされていないようです


CC BY-SA 4.0
2019-05-05T06:02:27.070
54734
2019-05-05T06:14:10.697
2019-05-05T06:14:10.697
3068
3068
54693
answer


一般的に完全に曖昧さを回避する方法があるか？というのは微妙ですが、今回のパターンなら、"型引数を取るか、取らないか？"という違いがあるため、型引数を明示することにより、(質問の直接の回答にはなっていませんが)可変長引数の方を呼ぶことは可能だとは思います。

[code]

    scala> net.sourceforge.argparse4j.ArgumentParsers.newFor("a").build().addArgument("b").setDefault("c")
    <console>:14: error: ambiguous reference to overloaded definition,
    both method setDefault in trait Argument of type [E](x$1: E*)net.sourceforge.argparse4j.inf.Argument
    and  method setDefault in trait Argument of type (x$1: Any)net.sourceforge.argparse4j.inf.Argument
    match argument types (String)
           net.sourceforge.argparse4j.ArgumentParsers.newFor("a").build().addArgument("b").setDefault("c")
                                                                                           ^
    
    scala> net.sourceforge.argparse4j.ArgumentParsers.newFor("a").build().addArgument("b").setDefault[String]("c")
    res0: net.sourceforge.argparse4j.inf.Argument = net.sourceforge.argparse4j.internal.ArgumentImpl@736cb954
    
[/code]

また、リフレクションで呼び出すにしても、わざと以下のようにstructural subtypingを使うと、多少短くかけて便利、などはあります。

[code]

    .asInstanceOf[{def setDefault(x: AnyRef)}].setDefault("c")
    
[/code]


CC BY-SA 4.0
2019-05-06T00:19:44.197
54761
2019-05-06T00:19:44.197
56
54693
answer


見ているところ、一般的な問題として見ると Scala の記述では明示的な解決方法はないようですね。  
argparse4j のケースでは返値を使ってメソッドチェーンとして使用していますので、Structual Subtyping を implicit
クラスで利用するようにしました。

[code]

    implicit class _Argument(arg:Argument){
      def setDefault2(value:Any):Argument = {  // ← 名前は適当ですが
        import scala.language.reflectiveCalls
        arg.asInstanceOf[{def setDefault(value:Object):Argument}].setDefault(value)
      }
    }
    
[/code]


CC BY-SA 4.0
2019-05-08T05:46:57.900
54816
2019-05-08T05:46:57.900
2827
54693
answer