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 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

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

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