smartword 0.0.2をリリースしました。重度のVim hackerな方からのプレッシャーを感じたため久々にコードを見直したところ、非常に残念なコードを書いていたことに気付いたため、バグ修正やリファクタリングなどを行いました。機能的には変化ありません。
日本語の移動の規則がよくわからない
Vimのword/WORD単位の移動は分かち書き言語を前提としているので、日本語などの言語における単語単位での移動はできません(実行時に形態素解析でもすれば別ですが、現状ではそういうことは考慮されていません。最近、vim_devでその手の話題がありましたが、あまり盛り上がってはいませんね: word segmentation in Vim - vim_dev | Google Groups)。
その上で、日本語を含むテキストにおけるword単位の移動は以下のような挙動になっています:
'iskeyword'の値による。ただしほとんどの場合、全てのマルチバイト文字が含まれている)。例えばwでの移動は下図のようになります(*はカーソルの移動位置)。
日本語を含むテキストにおけるword単位の移動の規則がよくわからないアルよ * * * * * * * * * * * * * * *
ただし、以上のことは私のVim経験をまとめただけなので、ソースコード的にどうなっているかは読んだことがないので不明です。
Vim組み込みのtext objectsのうち、記号系のものの使い分けが面倒になってきたので、カーソル周辺のテキストから適当な範囲を対象にするものが欲しくなってきた。
"foo *is* bar (a.k.a 'baz')" ^a ^a ^i | _|__ _|_________ _|__________________________
例えば上記のようなテキストがあるとして、カーソル位置が"^"の場合にaqまたはiqで"_"で示される範囲が対象となるというもの。
名前はtextobj-quotesでどうだろう。色々とフィジーで面倒なところはあるけど、ちょっと頑張ればできそうな気がする。今度時間ができたらやろう。
syntaxcomplete.vimの'iskeyword'の扱いが間違っている点を修正。十中八九Cで使えるようなキーワードしか補完候補に現れなかった。何故あのようなコードになっていたのだろうか。メンテナーには報告済み。
key-chord.elという、Emacsで複数キーの同時押しにコマンドを割り当てる機能を提供するものがある。初見で「ああ役に立たないな」とは思ったものの、試してみないことには何とも言えないので、Vim版のarpeggio.vimを書き、いくらか設定を追加したのが2ヶ月ほど前の話。今日になって再考してみたが、やはりVim使いには役に立たないという結論になった。
結局、Vim使いでこれを役に立つと思う人は極々稀だろうし、役に立つケースも極少数だろう。
(ついでに言えば上記の事情も踏まえて名前を「arpeggio」にした)
久々にCS beatmaniaIIDX15 DJ TROOPERSを起動。公式サイト以外の関連情報には一切触れていないため正確なところは分からないけれど、曲目からして今日プレイした分で隠し曲は出し尽したと思う。これで一通り終了かな。
今作もスタッフの力の入れようには脱帽せざるを得ない。挙げていくとキリがないので端折りますが、最後の隠し曲については特筆に値します。曲自体はおまけもおまけで、今作の収録曲数的にROMの残り容量も厳しいだろうからムービーは汎用だろうと思っていたし、実際にムービー冒頭もGOLD初出のそれだったので油断していました。演出としてはMilitary Splashの方が派手だしインパクトもあったのですが、あのムービー以上のものはないと思う。
後は説明書のアレが前例の再来とならないことを願うばかり。
function prompt-git-head-name() {
local git_dir="$(git rev-parse --git-dir 2>/dev/null)"
if [ -z "$git_dir" ]; then
return 1
fi
local head_name=''
local additional_info=''
if [ -d "$git_dir/rebase-apply" ]; then
if [ -f "$git_dir/rebase-apply/rebasing" ]; then
additional_info="REBASE"
elif [ -f "$git_dir/rebase-apply/applying" ]; then
additional_info="AM"
else
additional_info="AM/REBASE"
fi
head_name="$(git symbolic-ref HEAD 2>/dev/null)"
elif [ -f "$git_dir/rebase-merge/interactive" ]; then
additional_info="REBASE-i"
head_name="$(< "$git_dir/rebase-merge/head-name")"
elif [ -d "$git_dir/rebase-merge" ]; then
additional_info="REBASE-m"
head_name="$(< "$git_dir/rebase-merge/head-name")"
elif [ -f "$git_dir/MERGE_HEAD" ]; then
additional_info="MERGING"
head_name="$(git symbolic-ref HEAD 2>/dev/null)"
fi
if [ -z "$head_name" ]; then
head_name="$(git branch | sed -e 's/^\* //;t;d')"
if [ "$head_name" = '(no branch)' ]; then
# "git branch" doesn't show the correct name of a branch after
# "git checkout {commitish-and-not-the-head-of-a-branch}",
# so we have to use another method to get the name of {commitish}.
head_name="($(
{
fgrep 'checkout: moving from ' .git/logs/HEAD |
sed '$s/^.* to \([^ ]*\)$/\1/;t;d'
} 2>/dev/null
))"
elif [ "$head_name" = '' ]; then
head_name='(just initialized; nothing commited)'
fi
else
head_name="${head_name##refs/heads/}"
fi
if [ -n "$additional_info" ]; then
additional_info="|$additional_info"
fi
echo " [$head_name$additional_info]"
return 0
}Vimのabbreviations (略語展開)を文脈に応じて定義できるようにするプラグインctxabbrをリリースした。平たく言うと:abbreviateのラッパー関数。文脈の判定方法がまだこなれてないので何とも。
展開に際し<expr>相当のことをできるようにもしようかと思ったけれど、それをするなら'omnifunc'にした方がいいだろう。そして補完候補の絞り込みを緩いパターンマッチングでできるようにすればいい。例えば補完候補が"foo"、"foobar"、"fooujihisa"の3つならばfbだけで"foobar"を選べるようにするということ。
ただそれを実現するならば、既存の補完に対するラッパーとして実装した方が便利だろう。ユーザー定義の補完については対応する関数が提供されるので簡単に実現できるのだけど、組み込みの補完(例えば:help i_CTRL-X_CTRL-L)については対応する関数がないのでそうはいかない。組み込みの補完についてもユーザー定義の補完と同様の関数を提供するようパッチを書こうかと思ったけれど、結構入り組んでいるので全体の構成を把握していなければ書けない。どうしたものかな。
長らくKensington Orbit Opticalを使っているけれど、横スクロールができないので時々困る。ボールを前後に転がしたら上下にスクロール、ボールを左右に転がしたら左右にスクロール、という風にしたい。MouseWorksのScroll With Mouseで上下スクロールはできるのだけど、左右の成分は無視される。スクロール方向を上下と左右で切り替えることはできるのだけど、操作が面倒で使いものにならないし、切り替えのためにボタンが1つ潰れる。
どうにかできないかな。上下左右どちらも有効にした場合、まっすぐ上下スクロールしているつもりでも左右成分が微妙に反映されるという問題はありそうだけど、微小な量は無視することにすれば大丈夫だと思う。できたらできたで便利なので課題として考慮には入れておこう。
なお、先程、偶然にもXcodeのサンプルに「Trackball」というそれっぽい名前のものを発見したのだけれど、これはトラックボールのようなGUIコントロールのサンプルだった。非常にがっかりした。
ku 0.2.xでの高速化案の実装中。作業の前段階として$VIMRUNTIME/syntax/下のファイルを対象に100回ほど補完させてプロファイルを行った。予想通りではあるが、結果としてs:_omnifunc_core ('omnifunc'での補完候補のソート・フィルタリング)とs:_omnifunc_compare_lists (個々の補完候補をソートする際の比較関数)が最も重いと判明した。後者は一見して手のつけどころがないため、前者に対して取りかかった。
s:_omnifunc_core中で最も重要なのは補完候補のソート優先順序の算出。開発初期に分かり易さを重視して書いて以降、ずっとそのままだったため、改めて見直すと無駄が多い。具体的には以下の3点:
これだけで10倍程度は速度が向上した。今の私の環境では$VIMRUNTIME/syntax/下に520個のファイルがあるのだけれど、0.196秒程度で済むようになった。体感的には我慢できるレベル。欲を言えば後半分にしたいのだけど、s:_omnifunc_coreについて今のところ効果の高い高速化案は思い付かない。全体として見ればs:_omnifunc_compare_listsの実行時間の比率が結構あるのだけど、こちらも同様。
I've just thought of a puzzle: N-Quine. A program which generates a source code of a program which generates a source code of another program ... then the last one generates a source code of the first one, where N means the number of programs in this loop. For example, usual Quine can be treated as 1-Quine, because it generates a source code of itself and there is just 1 program in this loop.
But is it possible to write such program? Or, are such programs already written?
ku 0.2.xでの高速化案の実装中。本体側でのキャッシュを実装。詰めが残っているけれど、まあ大方はできた。
ただこれよりは'omnifunc'での候補のソートが重要な要因なので、そちらの方をやるべきだった。実際の使用においては、キャッシュはセッション間で効くようにしないと体感的に差はないし。
遅延評価のほうがモナドよりよっぽどおかしな振る舞いをするという点で難しい
you can see the difference between Prelude,foldl and Data.List.foldl' about strangeness of lazy evaluation, for example
let seefoldl (+) 0 [1..100000000000000]andimport Data.List; foldl' (+) 0 [1..100000000000000]
Twitterにて@ikegami__さんに教えてもらった。実際に試してみると確かにfoldlはスタックオーバーフローになる:
$ ghci GHCi, version 6.8.3: http://www.haskell.org/ghc/ :? for help Loading package base ... linking ... done. Prelude> foldl (+) 0 [1..1000000] *** Exception: stack overflow Prelude> import Data.List Prelude Data.List> foldl' (+) 0 [1..1000000] 500000500000
最初は何でだろうかと思ったけど、Haskellは遅延評価が基本なので、問題の式をfoldlの定義に沿ってoutermost reductionしていくと、長大なリストに対してそうなるのは自明でした。foldlの場合、戻り値は与えられたリスト中の要素全てを用いなければ求まらないため、outermost reductionではこのような問題が起きるのですね。
foldl :: (a -> b -> a) -> a -> [b] -> a foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs
foldl (+) 0 [1..1000000] -- foldl (+) 0 1:[2..1000000] foldl (+) ((+) 0 1) [2..1000000] -- foldl (+) ((+) 0 1) 2:[3..1000000] foldl (+) ((+) ((+) 0 1) 2) [3..1000000] ...
一方、foldl'はseqを用いてzの算出を先に終えるため、foldlのような問題は起きないことが分かります。
foldl' f z [] = z foldl' f z (x:xs) = let z' = f z x in z' `seq` foldl' f z' xs
foldl' (+) 0 [1..1000000] -- foldl' (+) 0 1:[2..1000000] -- let z' = (+) 0 1 in z' `seq` foldl' (+) z' [2..1000000] -- let z' = 1 in z' `seq` foldl' (+) z' [2..1000000] -- 1 `seq` foldl' (+) 1 [2..1000000] foldl' (+) 1 [2..1000000] -- foldl' (+) 1 2:[3..1000000] -- let z' = (+) 1 2 in z' `seq` foldl' (+) z' [3..1000000] -- let z' = 3 in z' `seq` foldl' (+) z' [3..1000000] -- 3 `seq` foldl' (+) 3 [3..1000000] foldl' (+) 3 [3..1000000] ...
しかし、いつseqを使うべきかは素人目にも難しいそうで困りましあ。foldlのようにリスト中の値を全て使う場合はoutermost reductionされると困ります。かといって下手にseqを使うと遅延評価によるメリットが損なわれるはずです。実際、foldl'はa strict version of
と説明されてはいるものの、foldlxsについては遅延評価のままですし。
とはいえ、この問題なら簡約手順さえ知っていれば容易に理解できることなので、モナドより難しいとは思えません。経験を積めば見解が変わるのでしょうか。
現在のメインマシンはMacBookで、基本的にリッドクローズドモードで据え置きマシンとして使用しているのだけど、稀に蓋を開けてデュアルモニター的に使う場合もある。
この時、MacBook側のモニターが不要になったからといってMacBookの蓋と閉じるとスリープしてしまう。確かにこの挙動自体は普通なのだけど、元々リッドクローズドモードで使っていた場合は蓋を閉じることでスリープされる方が不便だと思う。どうにかできないかなぁ。
仕方がないので、今のところ使わなくなったときはMacBookのモニターの輝度を最低に落としている。
合間合間、久々にハードコアポエムを読んでいるけれど、色々ときつい。刊行順に読んでいるのだけど、先鋒から既に最大出力なので、これを乗り越えると後はもう楽勝なような、そうでもないような。
ku 0.2.xでの高速化の案、正確には高速化するための予防線のメモ。これに合わせた実装はこれから。というか今日は寝過ぎた。
なんか、魔が差した。左から順に:
kuは九分九厘必要なところができたので、以降は設計上の不味い点を直そうと思う。取り敢えず大きいところとしては以下の2点が挙げられる:
他にも細々とした問題点はあるけれど、特に取り上げるほどのことではないので省略。