htsign's blog

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

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

ぐぬぬ

全てのフレームを対象にするには

某大手工業系企業の新規開発プロジェクトに参画しているのですが、非常に悲しいことに社内ポータルが未だにフレームを使用したサイトなのです。
フレームを使うと容易にペインで分けたサイトを構築できるため、実用重視のエンタープライズ界隈で重宝されるのはよく分かります。

ただ、フレーム*1を使うと、ブックマークレットを作ろうと思ったときにそのままではトップレベルのウィンドウにしか効力を発揮しません。
埋め込まれているフレームのソースURLがトップレベルのウィンドウと同じドメインに属しているのであれば*2、以下のようなコードを使うことで全フレームに対して操作することができます。

// for IE9+
(function loop(win) {
  return Array.prototype.reduce.call(win, function(p, c) {
    return p.concat(loop(c.window));
  }, [win]);
})(window)
.forEach(function(window) {
  var document;
  try { document = window.document; } catch(e) { return; }
  /* code here */
});
// compressed
(function l(w){return [].reduce.call(w,function(p,c){return p.concat(l(c.window))},[w])})(window).forEach(function(w){try{var d=w.document}catch(e){return}/* code here */})

*1:<iframe>も同様です。

*2:Same-Origin PolicyはWebにおける重要な概念です。