git stash の tips
git stash
、使ってますか?
作業中の内容を一旦別に追いやることで他のコマンドを阻害しなくなる便利なコマンドです。
git stash
の詳細については、Qiitaやら各種ブログやらで説明していらっしゃる先人がたくさんおりますので、そちらにお譲りします。
tl;dr
git stash <subcommand> stash@{<n>}
を省略して git stash <subcommand> <n>
と書ける。
本文
みなさんご存じの通り、Gitの各種コマンドには様々なサブコマンドがあり、この git stash
も例外ではありません。
この内、
git stash show
git stash apply
git stash pop
git stash drop
git stash branch
は何も引数を与えず実行すると、暗黙的に stash@{0}
が採用されたものと見做されます1。
で、引数を与える場合、特にstashの番号を与える場合は stash@{2}
とか stash@{3}
とか、 stash@{n}
のフォーマットで書きます。
これ、数タイプではありますが結構面倒くさいですよね。typoの可能性だって出て来ます。
GitはコミットをSHA1のハッシュで管理しています。
多くのGitコマンドはコミットの指定にハッシュ値の入力を要求しますが2、このハッシュ値は現在のリポジトリの中で一意に特定さえできれば、最低4文字まで省略できることになっています。
例えば、リポジトリに caf1234...
というコミットがあり、 他に caf1
で始まる他のコミットがなければ、
git checkout caf1
でこのコミットにチェックアウトすることができます。
caf10...
というコミットが他にあれば caf1
では候補が2つあることになってしまうので、
一文字追加して git checkout caf12
でチェックアウトできますね3。
で、私思いました。ハッシュ値が省略できるなら、もしかしたらstashの指定も省略できるのではないかと。
何気なく git stash drop 1
と入力して実行してみたら、何と、通りました。
ドキュメントを読んでみるとこんな記述があります。
The latest stash you created is stored in
refs/stash
; older stashes are found in the reflog of this reference and can be named using the usual reflog syntax (e.g.stash@{0}
is the most recently created stash,stash@{1}
is the one before it,stash@{2.hours.ago}
is also possible). Stashes may also be referenced by specifying just the stash index (e.g. the integern
is equivalent tostash@{n}
).
太字部分を雑に訳すと「インデックスだけを指定して参照することもできます(例えば n
は stash@{n}
と等価です)」といった感じでしょうか。
恥ずかしながら、今までこの略記を知りませんでした。
いろいろな git stash
に言及している記事を読んでもこの略記について触れているものは私の観測範囲にはありませんでした。
意外と知られていないのかな、と思ってこのエントリを書きました。
ちなみに HEAD
も @
という略記がありますが、これは結構有名ですよね。
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
が消え去るのかの原因も分かる方いらっしゃれば…。
式途中に null として評価されてしまうケースの回避方法
F# の話題です。
let x = A().B.C.D
とあるとき、もしA().B
が null
だったら?
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 }
さっきよりは大分マシになりましたね。
まぁこれでいいんですが、もう一歩やってみました。
クッソ長いんですが、要は評価したい式を引用式として与えることで、途中で null
を検出したら全体を null
とするってことです。
null
を None
、非 null
を Some 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().B
が null
だったとしても 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");
とする必要があったようです。
理由について多少なり調べてみましたが、どうも納得のいく結論には達しませんでした。
「そういう仕様だから」ということで終わりにします。