Java5 での開発のつらみをやわらげる tips
仕事で Java5 の案件がきました。
このご時世に Java5 とか負の遺産以外のなにものでもないですが、仕事なので仕方ありません。
拡張for文とか可変長引数とかアノテーションとかジェネリクスとか、それ以前と比べればかなりマシなものの、
現代的なプログラミング言語の数々と比べてしまうとゴミカスだと思います。
何よりツラいのが、高階関数がないこと。
高階関数がないので副作用を抑えるコードを書きづらいのです。
仕方がないのでjava.lang.reflect.Method
クラスを使います。
こんなクラスを書きました。
追記 (2017/11/16 11:23)
長くて記事の本質が見えにくくなってしまうので、Gistに移してリンクにしました。
It provides some functional programming feature to Java5 · GitHub
これはこれで毎度Method
クラスのインスタンスを作らなくてはならないとか型チェックが効かず実行時エラーの恐れがあるとか別のつらみがありますし、
内部でもインスタンス作りまくったりしてて非常にパフォーマンスは悪いと思いますが、
引き換えにちょっとだけ関数型ちっくに書くことができるようになりました。
import java.io.PrintStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; class Example { public static void main(String[] args) { // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 の ArrayList final List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < 10; ++i) { list.add(Integer.valueOf(i)); } try { final Method isEven = Example.class.getMethod("isEven", int.class); final Method stringValueOf = String.class.getMethod("valueOf", int.class); final Method println = PrintStream.class.getMethod("println", String.class); // メソッドチェイン的な new IterableUtils<Integer>(list) // IterableUtils インスタンスに変換 .filter(isEven) // 2 で割り切れる数のみにフィルタ .map(String.class, stringValueOf) // String 型にマッピング .forEach(System.out, println); // 一つずつ System.out に出力 ==> 0, 2, 4, 6, 8 final Method plus = Example.class.getMethod("plus", int.class, int.class); // モジュールそのまま呼ぶ感じ // 畳み込んで合計 System.out.println(IterableUtils.reduce(list, 0, plus)); // ==> 45 } catch (Exception e) { e.printStackTrace(); } } public static boolean isEven(int i) { return i % 2 == 0; } public static int plus(int lhs, int rhs) { return lhs + rhs; } }
うん、問題なく動いてるように見えますね。
これでゴテゴテのオブジェクト指向言語に無理矢理関数型パラダイムを持ち込むことができました。
この手のライブラリってどっかにありそうなもんなんですが、探してもそれらしいの見つからなかったんですよね…。
Perl の range operator
これPerlerの皆さまからすれば「何を今更」な話かもしれませんが、個人的に驚いたので書きます。
Perlには範囲演算子というのがあって、他の言語でも度々採用されてる便利な演算子なんですが、
例えば
my @nums = (1..5);
とすることで 1 から 5 までの連番を含む配列を定義できます。
単純な文字列なら、
my @strings = ("aa".."ae"); print "@strings\n"; #==> aa ab ac ad ae
とかもできますね。
さて、この2例目の("aa".."ae")
みたいな文字列の範囲を配列にするにあたって、
# これは期待通り my @arr1 = ("A".."Z"); #==> A B C D E F G H I J K L M N O P Q R S T U V W X Y Z # これも期待通り my @arr2 = ("a".."z"); #==> a b c d e f g h i j k l m n o p q r s t u v w x y z # じゃあこれは…? my @arr3 = ("A".."z");
私は3つ目の出力はA B C ... X Y Z [ \ ] ^ _ ` a b c ... x y z
となると期待しました。
なぜなら私の知る他の言語では同じように組むとそういう結果になるからです。
でもPerlではA B C D E F G H I J K L M N O P Q R S T U V W X Y Z
でした。
期待する結果を得るには
my @arr4 = map {chr} (ord "A"..ord "z");
とする必要があったようです。
理由について多少なり調べてみましたが、どうも納得のいく結論には達しませんでした。
「そういう仕様だから」ということで終わりにします。
JScript(not JavaScript)のArrayに対するメソッド群を追加する超単純なポリフィル
今さらこんなコードを書くこと自体恥ずべきことだと思うのだけど、勤務先の業務システムがJSPで実装されており、クライアントサイドはレガシーなIEが想定されています(嘆かわしいですが、よくある光景ですね…)
そしてこれまたよくある光景なのですが、秘伝のソースであるため、
<%-- yyyy/mm/dd add someone IE11対応 --%> <META HTTP-EQUIV=X-UA-Compatible CONTENT=IE=5> <%-- /IE11対応 --%>
みたいなのが書いてあるわけです。
お前それどこがIE11対応やねん、といったツッコミはさておき。
そんな環境ですと、おもむろにF12
を押してデバッグをしようにも配列やArrayLikeを扱う上で非常につらみを感じるわけです。
「じゃあlodash使えばいいじゃん!」と
(function(s){ s.src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"; document.body.appendChild(s); })(document.createElement("script"))
なんてコードを実行してみても、lodashの実行中にコケたりするんです。
そのときに知ったんですが、lodashってIE9+なんですね…。
便利なライブラリが使えないとなると、ある程度自分で書かざるをえないのでブックマークレットを作成して活用しています。
というのが前置き。
当たり前ですがコードの持ち出しは禁止なので、実際のコードとはちょっと違うと思います。
javascript:(function(d,i,l,x,a){d("indexOf",function(v){for(i=0,l=this.length;i<l;++i)if(this[i]===v)return i;return -1});d("includes",function(v){return!!~this.indexOf(v)});d("map",function(f,t){x=this,a=[];for(i=0,l=x.length;i<l;++i)a.push(f.call(t,x[i],i,x));return a});d("filter",function(f,t){x=this,a=[];for(i=0,l=x.length;i<l;++i)f.call(t,x[i],i,x)&&a.push(x[i]);return a});d("forEach",function(f,t){x=this;for(i=0,l=x.length;i<l;++i)f.call(t,x[i],i,x)});d("reduce",function(f,v){x=this,v=v!==void 0?v:x.shift();for(i=0,l=x.length;i<l;++i)v=f(v,x[i],i,x);return v});d("every",function(f,t){return!!this.reduce(function(p,c){return p&&f.call(t,c)},1)});d("some",function(f,t){return!!this.reduce(function(p,c){return p||f.call(t,c)},0)})})(function(n,f){if(![][n])Array.prototype[n]=f})
追記 (2018/06/11 01:59)
改めてGistに起こしました。
ES5 or above polyfills for ES3 (It's not compatible with ECMA-262 strictly) · GitHub
len
という変数を定義してまでいちいちthis.length
を代入している理由は、賢明な諸兄はもちろんご存知かと思いますが、forループで毎回Array#length
にアクセスしてパフォーマンスが落ちるからです。
なので最初に長さを持ってしまいます。
その結果、高階関数に渡す関数がオブジェクト自身の要素数に副作用を起こすものである場合、意図しない動作になります。
まぁ自分しか使わないのでどうでもいいんですが。
Partial Active Patterns
引数で受けた正規表現を評価して、ヒットしたら返します。
習作として作りました。
使い方
アクティブパターンなので、パターンマッチの内部でリストから取り出すイメージですね。
マッチしなければ次のパターン(この例では| _ -> ()
の行)に評価が移ります。
今回はデモなのでリストをそのまま出力しています。
open RegExp let testString = "abc123def456ghi789" let testPattern = @"(?<name>[a-z])+(?<number>[0-9])+" // 単純なマッチ match testString with | Match testPattern result -> printfn "%A" result | _ -> () // 単純なマッチの集合 match testString with | Matches testPattern result -> printfn "%A" result | _ -> () // 名前付きマッチ match testString with | NamedMatch testPattern result -> printfn "%A" result | _ -> () // 名前付きマッチの集合 match testString with | NamedMatches testPattern result -> printfn "%A" result | _ -> () (* 上記 result の部分もパターンマッチなので、 うまいことリストのリテラルを書いて受けると、そのままリストの中身を取り出すこともできます。 参考にしたMicrosoft Docsのドキュメントにはこの方法が書かれています。 *) match testString with | Match testPattern [e1; e2] -> printfn "e1: '%s'\ne2: '%s'" e1 e2 | _ -> ()
出力
// 単純なマッチ ["c"; "3"] // 単純なマッチの集合 [["c"; "3"]; ["f"; "6"]; ["i"; "9"]] // 名前付きマッチ map [("name", "c"); ("number", "3")] // 名前付きマッチの集合 [map [("name", "c"); ("number", "3")] map [("name", "f"); ("number", "6")] map [("name", "i"); ("number", "9")]] // 下のリストで受けたやつ e1: 'c' e2: '3'
これだけあれば、とりあえずはだいたいのニーズを満たせるのではないかと思います。
参考: Active Patterns (F#) | Microsoft Docs
追記
名前付きマッチの返り値をMap
にしました。
添え字でアクセスできるようになります。