htsign's blog

ノンジャンルで書くつもりだけど技術系が多いんじゃないかと思います

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".."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})

これに改行やインデントを付けるとこんな感じになり、

(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
})

変数名や構文に修正を加えるとこうなります。

(function(define) {
  var i, len, arr;
  
  define("indexOf", function(value) {
    for (i = 0, len = this.length; i < len; ++i) {
      if (this[i] === value) return i;
    }
    return -1;
  });
  
  define("includes", function(value) {
    return this.indexOf(v) !== -1;
  });
  
  define("map", function(func, thisArg) {
    arr = [];
    for (i = 0, len = this.length; i < len; ++i) {
      arr.push(func.call(thisArg, this[i], i, this));
    }
    return arr;
  });
  
  define("filter", function(func, thisArg) {
    arr = [];
    for (i = 0, len = this.length; i < len; ++i) {
      if (func.call(thisArg, this[i], i, this)) a.push(this[i]);
    }
    return arr;
  });
  
  define("forEach", function(func, thisArg) {
    for (i = 0, len = this.length; i < len; ++i) {
      func.call(thisArg, this[i], i, this);
    }
  });
  
  define("reduce", function(func, value) {
    if (value === void 0) {
      value = this.shift();
    }
    for (i = 0, len = this.length; i < len; ++i) {
      value = func(value, this[i], i, this);
    }
    return value;
  });
  
  define("every", function(func, thisArg) {
    return !!this.reduce(function(prev, curr) {
      return prev && func.call(thisArg, curr);
    }, true);
  });
  define("some", function(func, thisArg) {
    return !!this.reduce(function(prev, curr) {
      return prev || func.call(thisArg, curr);
    }, false);
  });
})(function(name, func) {
  if (Array.prototype[name] === void 0) Array.prototype[name] = func;
});

indexOfincludesに第二引数(探査を開始するインデックス)を定義していないのは必要ないからです。
アレ活用している人いるんですかね?

正直map filter forEachthisArgも使わないと言えば使わないんですが、なんとなく付けました。

lenという変数を定義してまでいちいちthis.lengthを代入している理由は、賢明な諸兄はもちろんご存知かと思いますが、forループで毎回Array#lengthにアクセスしてパフォーマンスが落ちるからです。
なので最初に長さを持ってしまいます。
その結果、高階関数に渡す関数がオブジェクト自身の要素数に副作用を起こすものである場合、意図しない動作になります。

まぁ自分しか使わないのでどうでもいいんですが。

 

 

話戻りますが、前述のJSPのコード、それはそれは本当に酷いです。

var url;

if (document.forms[0].document.forms[0].foo.value == "1") {
  document.forms[1].elem1.disabled = true;
  document.forms[1].elem2.disabled = true;
  document.forms[1].elem3.disabled = true;
  
  url = "/foo.html";
  document.forms[2].location.href = url;
  return;
}
else if (document.forms[0].document.forms[0].foo.value == "2") {
  document.forms[1].elem1.disabled = true;
  document.forms[1].elem2.disabled = true;
  document.forms[1].elem3.disabled = true;
  
  url = "/bar.html";
  document.forms[2].location.href = url;
  return;
}
else if (document.forms[0].document.forms[0].foo.value == "3") {
  document.forms[1].elem1.disabled = true;
  document.forms[1].elem2.disabled = true;
  document.forms[1].elem3.disabled = true;
  
  url = "/baz.html";
  document.forms[2].location.href = url;
  return;
}
else {
  document.forms[1].elem1.disabled = true;
  document.forms[1].elem2.disabled = true;
  document.forms[1].elem3.disabled = true;
  
  url = "/qux.html";
  document.forms[2].location.href = url;
  return;
}

みたいなJSが平気で埋め込まれていたりして、「DRYとはいったい…ウゴゴゴゴ」という気持ちでいっぱいになります。
クライアントサイドを担当した人すごい。

商業システムってこんなんばっか。
これで目立ったバグがないならまだいいんだけど、たまに動かなくなるからな…。

【参考】

unkode-mania.net

はてブのコメントにつけたスターを見易くするグリモンスクリプトがはてブリニューアルにつき動かなくなったので修正しました。

表題の通り。

gist.github.com

Partial Active Patterns

引数で受けた正規表現を評価して、ヒットしたら返します。
習作として作りました。

gist.github.com

使い方

アクティブパターンなので、パターンマッチの内部でリストから取り出すイメージですね。
マッチしなければ次のパターン(この例では| _ -> ()の行)に評価が移ります。
今回はデモなのでリストをそのまま出力しています。

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にしました。
添え字でアクセスできるようになります。

はてブ見ててどのコメントにスター付けたかよく忘れるので

GreaseMonkeyスクリプトを書きました。

gist.github.com

使うと自分が付けた星は点滅するようになります。
以上。

F#のべき乗計算

現在F#の勉強を始めたところで、基礎的なところをぼちぼち習得している段階です。

演算子の話なんですが、F#にはOCamlの系譜だからなのか**演算子(べき乗)があります。
RubyPythonなど、関数型の血を引いたいくつかの言語にも備わっている珍しくもない演算子なんですが、
なんとこの演算子、F#においては浮動小数点型でしか使えないのです…。
コンパイルするとMath.Pow : float * float -> floatになるようなので、当然と言えば当然ですね…)

なので整数計算で**演算子を使用したい場合は、

let i = 2 ** 3 // ここでコンパイルエラー
printfn "%i" i

ではダメで、

let i = 2.0 ** 3.0 |> int
printfn "%i" i // 8

などとしなくてはならず、少々面倒くさいです。

どうしてもという場合にはpown関数がありますが、あくまで関数なので演算子のように中置はできない模様。

let i = pown 2 3
printfn "%i" i // 8

// パイプ演算子を使うと中置っぽくなるが、アホっぽい
let i2 = 2 |> pown <| 3
printfn "%i" i2 // 8

ぐぬぬ