htsign's blog

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

Visual Studio Code for Windows の Integrated Terminal に MSYS2 の fish を使う

fish、便利です。

当方は未だにWindows 8.1 UpdateなのでWSL1は使えません。
なので Git for Windows SDK をインストールして簡易的なシェル環境を構築してます。

これが pacman があるのでなかなか快適なんですね。
nodeもこれで入れられるのでchocolateyもほぼ不要になります。

Git for Windows の本来のシェルは Git Bash ですが、他のシェルも pacman 経由でインストールできるため、私は fish をインストールしてこれをデフォルトにしています。

本題です。
慣れたシェルを使っていると、あらゆる場面で導入したくなるもので、VSCode の Integrated Terminal にも fish を導入したくなりました。

で、おもむろに Ctrl + , で設定画面出しますね。

{
    "terminal.integrated.shell.windows": "C:/git-sdk-64/usr/bin/fish.exe",
    "terminal.integrated.shellArgs.windows": [
        "-i",
        "--login",
    ],
}

と入力します。2
Ctrl + @ でターミナルを表示するとどうでしょう、エラーの嵐になります。
いろいろ調べた結果、 $PATH がほとんど空っぽになっていることが分かりました。

"C:/git-sdk-64/usr/bin/fish.exe" の代わりに "C:/git-sdk-64/usr/bin/bash.exe" と書いた場合は何の問題もなく起動し、パスも普通に通ってしまうので謎です。

ちなみに bash でうまく行くからと言って

{
    "terminal.integrated.shell.windows": "C:/git-sdk-64/usr/bin/bash.exe",
    "terminal.integrated.shellArgs.windows": [
        "-c",
        "/usr/bin/fish",
    ],
}

としても無駄です。
盛大にエラります。

結構な時間を試行錯誤でアレコレし、

{
    "terminal.integrated.shell.windows": "C:/git-sdk-64/usr/bin/fish.exe",
    "terminal.integrated.shellArgs.windows": [
        "-c",
        "eval 'set -x PATH ~/bin /mingw64/bin /usr/bin $PATH; and /usr/bin/fish'",
    ],
}

と書くことでひとまず解決しました。

fish では変数にスペース区切りで値を与えると配列として格納されます。
$PATH も配列で持っているので、その先頭に強引にパスを与えてしまいます。
fish.exe をコールしている段階では $PATH は無事のようなので、ここで set -x PATH ... としてしまいました。3

VSCode には、シェルに与える環境変数をオーバーライドする terminal.integrated.env.windows という設定値もあるのですが、ここだと ~$PATH が展開されない為、已む無くコマンド評価で与えています。

正直言ってワークアラウンド的でスマートさに欠けるので、もっといい方法があれば是非教えていただきたいですね。
あとなぜ $PATH が消え去るのかの原因も分かる方いらっしゃれば…。


  1. Windows Subsystem for Linux

  2. 余談ですが、実は Windows でもほとんどの場面でパスの区切り文字にバックスラッシュ「\」の代わりにスラッシュ「/」を使っても問題ないです。

  3. fish における set -x FOO barbash で言う export FOO=bar のようなもの。

式途中に null として評価されてしまうケースの回避方法

F# の話題です。
let x = A().B.C.D とあるとき、もしA().Bnull だったら?
F# で定義した型は AllowNullLiteralAttribute 属性を付与するか Unchecked.defaultof<'a> を使用しない限り、たとえ参照型だろうが null になることはありません。
でもCLRのクラスは null の可能性があります。
.NET Framework の資産を使う以上、 NullReferenceException と付き合っていく必要はどうしてもあります。

一番愚直な方法は、いわゆる nullチェックです。

let x =
    match A() with
    | null -> null
    | x ->
        match x.B with
        | null -> null
        | x ->
            match x.C with
            | null -> null
            | x -> x.D

…ツラすぎる。

次、Maybeモナドみたいなコンピュテーション式を使う方法。

[<AutoOpen>]
module Maybe =
    type MaybeBuilder() =
        member __.Bind (x, f) = if x = null then null else f x
        member __.Return x = x
    let maybe = MaybeBuilder()

let x = maybe {
    let! x = A()
    let! x = x.B
    let! x = x.C
    return x.D }

さっきよりは大分マシになりましたね。

まぁこれでいいんですが、もう一歩やってみました。

gist.github.com

クッソ長いんですが、要は評価したい式を引用式として与えることで、途中で null を検出したら全体を null とするってことです。
nullNone、非 nullSome x として見れば、まさにMaybeモナドがやっていることとニアリーイコールの結果を得られるわけです。

Gist のコメントに書いたやつのコピペですが、

open System
open QuotationUtility

let nullUri : Uri = null
let result1 = Quotation.evaluate <@ "not null value" @>
let result2 = Quotation.evaluate <@ (Uri("http://example.com/")).AbsoluteUri @>
let result3 = Quotation.evaluate <@ (null : Uri).AbsoluteUri @>
let result4 = Quotation.evaluate <@ nullUri.AbsoluteUri @>

printfn "1 : [%A] / 2 : [%A] / 3 : [%A] / 4 : [%A]" result1 result2 result3 result4
(* 1 : ["not null value"] / 2 : ["http://example.com/"] / 3 : [<null>] / 4 : [<null>] *)

大体こんな感じになります。
なんとなーく動いてるように見えますね?
上の例で言えば let result = Quotation.evaluate <@ A().B.C.D @> とすることで、仮に A().Bnull だったとしても result には null が束縛されるだけです。もちろん D まで正しく評価できれば D の値が束縛されます。

たぶんバグあるので複雑な式を与えるとエラると思いますが、少なくとも単純な式なら null を検出できているようです。
これを使えばスマートに nullチェックできるんじゃないでしょうか。
リフレクションなので、評価回数が増えてくると速度低下に繋がるところがネックですね。

ところで、F#のシンタックスハイライト、なんか毒々しいですね。

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にアクセスしてパフォーマンスが落ちるからです。
なので最初に長さを持ってしまいます。
その結果、高階関数に渡す関数がオブジェクト自身の要素数に副作用を起こすものである場合、意図しない動作になります。

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