Javaプログラマのみなさんは、Javaは型推論がないから変数の型指定をしなくていけなくてダサい、などとイジメられた経験があると思います。
おかあさんに型推論をねだるとGroovyをわたされたり、おとうさんに型推論をねだるとScalaがやってきたり、プレステが欲しいって言ったのにWiiやXboxを買い渡される感を味わった人も多いのではないでしょうか。
そんな良い子のJavaプログラマのために、今年はサンタが素敵なプレゼントを持ってきてくれましたよ。
同じ型を書くのがダサい
たとえばウィンドウを出してボタンを押したらメッセージが表示されるサンプルを書くとこんな感じになりますね。
public static void main(String... args){ JFrame f = new JFrame("テスト"); JButton b = new JButton("押して"); JTextArea ta = new JTextArea(); f.add(b, BorderLayout.NORTH); f.add(ta, BorderLayout.CENTER); b.addActionListener(ae -> ta.append("押された\n") ); f.setSize(400, 300); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); }
変数定義はこんな感じになってます。
JFrame f = new JFrame("テスト"); JButton b = new JButton("押して"); JTextArea ta = new JTextArea();
JFrame、JButton、JTextAreaが2回ずつ出てて、やっぱダサい。
入力はいろいろ補完が効くので、2回入力する必要はないんですけど。なので、2回入力するのが面倒という批判は的外れです。見た目がダサいのです。個人的には「new JFrame("テスト");」と打って、「新しい変数に戻り値を割り当て」とするのが好みです。
Let's let.
ということでサンタがくれたプレゼント。
こんなメソッド
public static<T> void let(T value, Consumer<T> cons){ cons.accept(value); }
このメソッドを使うと、さっきのコードはこうなります。
public static void main(String[] args) { let(new JFrame("テスト"), f -> let(new JButton("押して"), b-> let(new JTextArea(), ta ->{ f.add(b, BorderLayout.NORTH); f.add(ta, BorderLayout.CENTER); b.addActionListener(ae -> ta.append("押された\n") ); f.setSize(400, 300); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); }))); }
変数の型指定がなくなりました!
これで、おまえんち型推論がないとか言ってイジメられることはなくなりますね!
戻り値が必要な場合
ところで、先ほどのletでは、値が必要になる場合に対応できません。
ということで、値を返す版としてletRを定義します。
static<T, R> R letR(T value, Function<T, R> func){ return func.apply(value); }
そうすると、こんな感じで平方根の計算ができます。
System.out.println( letR(2, target -> DoubleStream.iterate(target, x -> letR(x * x - target, num -> letR(2 * x, den -> x - num / den ))) .skip(5).findFirst().orElse(0) ) );
結果はこんな感じに。
1.4142135623730951
これ素敵なのは、式の途中で変数が使えることですね。
関数型っぽいです。
式の内容については、ここらへんが参考になるかと。
BigDecimalで平方根を求めてみる
型推論には限界がある
平方根のコードでもうひとつ変数を導入してみます。
System.out.println( letR(2, target -> DoubleStream.iterate(target, x -> letR(x * x - target, num -> letR(2 * x, den -> letR(num / den, frac -> x - frac )))) .skip(5).findFirst().orElse(0) ) );
そうするとコンパイルエラーになってしまいます。
不適合な型: ラムダ式の戻り型が不正です 推論型が上限に適合しません 推論: R 上限: Double,Object
どうも、ターゲット型推論が2段くらいまでしか見てない感じで、denかfracのletRの前にキャストを入れて明示的に型を指定してあげると大丈夫です。
System.out.println( letR(2, target -> DoubleStream.iterate(target, x -> letR(x * x - target, num -> letR(2 * x, den -> (double)letR(num / den, frac -> x - frac )))) .skip(5).findFirst().orElse(0) ) );
型推論が3つ続かなければ大丈夫っぽい。b120で試してます。