こんにちわちわ。Underbar.phpの記事ぶりになりました@emonkakです。
本エントリでは、以前のエントリでお伝えした勤怠管理アプリケーションのシュキーンの開発について述べたいと思います。
シュキーンとは
シュキーンはAndroidで動作する勤怠管理アプリケーションです。打刻はNFCタグをAndroid端末にかざすことで行います。勤怠データはAndroid端末からサーバーに送信されるので、ネットワーク環境さえあればどこからでも確認することがきます。
2014年05月16日 (金)
こんにちわちわ。Underbar.phpの記事ぶりになりました@emonkakです。
本エントリでは、以前のエントリでお伝えした勤怠管理アプリケーションのシュキーンの開発について述べたいと思います。
シュキーンはAndroidで動作する勤怠管理アプリケーションです。打刻はNFCタグをAndroid端末にかざすことで行います。勤怠データはAndroid端末からサーバーに送信されるので、ネットワーク環境さえあればどこからでも確認することがきます。
2013年08月01日 (木)
こんにちわちわ。
以前に便利なVimのOperatorをマスターするを書きましたemonkakです。
今回は私が個人的に開発を進めていて以前に社内勉強会で発表しました、PHPのコレクション処理ライブラリのUnderbar.phpの紹介をしたいと思います。
PHPには配列処理のための関数がたくさんありますが、array_map()
の引数の順番が変だったりarray
がそもそもオブジェクトではないのでメソッドチェインができなかったり使い勝手が良くありません。
$xs = range(1, 10); $twice = function($n) { return $n * 2; }; array_slice(array_map($twice, $xs), 0, 3); // [2, 4, 6]
また、これらの関数はarray
専用なので、Iterator
のようなTraversable
なオブジェクトを引数に取ることはできませんし、処理を遅延してIterator
を返すなんてことも当然できません。
そこでarray
とTraversable
なオブジェクトを同じように扱えて、Iterator
を利用して遅延リストを返すことができるライブラリが欲しいですよね。
さらにPHP5.5から導入されたGenerator
も使えるといいですね!
ここで一度、遅延リストの恩恵について考えてみます。
0からの連番をたくさん返すIterator
の$ys
があります。
ここから偶数の要素を二乗して5つだけ取り出したいとします。
foreach
を使って自前でループしてbreak
するのが効率的なのは当然ですが、ここでは高階関数を使って書いてみます。
$even = function($n) { return $n % 2 === 0 }; $square = function($n) { return $n * $n; }; array_slice(array_map($square, array_filter(iterator_to_array($ys, false), $even)), 0, 5); // [0, 4, 16, 36, 64]
このコードには効率上の問題が2つあります。
Iterator
の長さ分だけ空間を必要とする$square
が呼びだされてしまう自前でループを書いて処理すればいいのですが面倒ですね。
それなら以下のような感じでIterator
をチェインして遅延リストとして処理してしまいましょう。
$xs = new TakeIterator(new MapIterator(new FilterIterator($ys, $even), $square), 5); iterator_to_array($xs); // [0, 4, 16, 36, 64]
これでわざわざループを書かなくても済みますし、効率上の問題も解決しました。
Underbar.phpの実装もまさにこのようになっています。
Underbar.phpはこれまでに述べた問題を解決するために開発されたPHPのコレクション処理のためのライブラリです。
その名の通りUnderscore.jsにインスパイアされたライブラリですが、移植というわけではないのでそれほど互換性は重要視していません。
詳しい使い方は公式ドキュメントを読んで頂くとして、
遅延リストを利用してフィボナッチ数列を10個取り出す例を引用してみます。
use Underbar\IteratorImpl as _; // Fibonacci sequence echo _::chain([1, 1]) ->iterate(function($pair) { return [$pair[1], _::sum($pair)]; }) ->map(function($pair) { return $pair[0]; }) ->take(10) ->join(); // 1,1,2,3,5,8,13,21,34,55
今回の例ではIterator
を返すIteratorImpl
を使っていますが、他にArrayImpl
とGeneratorImpl
があります。
PHPのarray
が扱いづらい問題はPHPerの共通見解のようでUnderbar.php以外にもいくつかコレクション処理ライブラリが開発されています。
大きくUnderscore.js系とLINQ系に分けることができます。
自分も1つ増やしてしまったわけですが、ちょっと乱立気味ですね。
本来こういった機能は言語の組み込みにするなり、何か統一を図るべきなのかもしれません。
まあPHPなのでしょうがないですね!
この中だと本家のUnderscore.jsが即時実行する実装なのもあって、遅延実行できる実装はありません。
これがUnderbar.phpの最初の開発の動機でした。
LINQ系のライブラリも結構数がありますが、最後発のGinqが設計が洗練されていて非常に良くできています。このスライドで大体の雰囲気を掴めるかと思います。
LINQ好きはGinq使っておけば間違いないですね。
Underbar.phpにはないjoin
によるデータの結合やソートの合成などもあるのでおすすめです。
PHPの配列を扱う関数は非常に使い勝手が悪いので、様々なコレクション処理のためのライブラリが開発されています。
しかし、中でも遅延リストを扱えるものは少ないので、そういった意味ではUnderbar.phpが選択肢の一つになりうると思います。
Underbar.phpは今後社内でも利用しようという動きがありますが、まだ誕生したばかりのライブラリなので不具合や要望等あればpull requestかIssuesの方にお願いします。
最後に社内勉強会でのUnderbar.php発表の様子とスライドです。
2011年11月28日 (月)
こんにちわちわ、10月に新しく入りましたemonkakです。
今回は全国1000万人のVimmerのみなさんに、便利なVimのoperatorについて紹介したいと思います。
opepatorとはお馴染みy
(yank)d
(delete)などの、範囲を取るコマンドのことです。
operatorはカーソルを移動するw
やe
などのmotionに対して使うか、 Visual mode上で使うことができます。
例えば、行の終わりまでyankする場合はy$
かv$y
と打鍵します。
また、operatorはtext-objectに対して使うことができます。
例えば、yiw
と打鍵すると現在のカーソルの下の単語をyankするといった動作になります。
この場合y
がoperatorで、iw
が範囲を示すtext-objectです。
yy
のようにoperatorのマッピングを二回打鍵した場合は、現在の行を表わすtext-objectを渡したことになります。
operatorはtext-objectと組み合わせると非常に便利です。
Visual modeで選択してからopepatorを使うより、text-objectに対して使う方がタイプ数が少なくて済むからです。
これでもうVisual modeは不要ですね。
ここで一度、組込みのoperatorとtext-objectをおさらいしておきましょう。
以下がデフォルトで定義されているoperatorです。
c
d
y
とインデント関連以外はあまり使うことはないかもしれません。
ROT13のoperatorがあるのは面白いです。
マッピング | 動作 |
---|---|
c |
変更する |
d |
削除する |
y |
yankする |
~ |
大文字小文字を入れ替える (‘tildeop’がセットされていれば) |
g~ |
大文字小文字を入れ替える |
gu |
小文字にする |
gU |
大文字にする |
! |
外部プログラムでフィルターする |
= |
インデントを整形する |
gq |
テキストを整形する(幅を80に収めたり) |
g? |
ROT13で暗号化する |
> |
右にインデントする |
< |
左にインデントする |
zf |
foldを定義する |
g@ |
‘operatorfunc’に設定された関数を呼び出す |
text-objectは通常a?
、i?
というルールで定義されています。
aは英語の冠詞aを表わし、範囲をそのまま示します。
iはinnerの略でその範囲の内部を示します。
例えば、ib
は括弧の内部を示し、ab
は括弧自体を含めた内容を示します。
マッピング | 範囲 |
---|---|
aw iw |
単語(iskeywordに依存) |
aW iW |
単語(空白区切り) |
as is |
文 |
ap ip |
段落 |
a] a[ i] i[ |
[] 括弧 |
a) a( ab i) i( ib |
() 括弧 |
a> a< i> i< |
<> 括弧 |
at it |
htmlタグ |
a} a{ aB i{ i} iB |
{} 括弧 |
a" i" |
ダブルクオート |
a' i' |
シングルクオート |
a` i` |
バッククオート |
また、textobj-userを導入すると新しくtext-objectを追加できます。
これでより便利にoperatorを使うことができます。
以下に公開されている有用なtext-objectを列挙しておきます。
使用にはtextobj-userが必要です。
text-object | 範囲 |
---|---|
textobj-datetime | 日付と時刻 |
textobj-diff | Diff hank |
textobj-entire | バッファ全体 |
textobj-fold | Folding |
textobj-function | 関数 |
textobj-indent | インデントブロック |
textobj-jabraces | 日本語の鉤括弧など |
textobj-lastpat | 最後に検索されたパターン |
textobj-syntax | Syntax highlightの塊 |
textobj-comment | コメント |
textobj-between | 任意の文字に囲まれた範囲 |
textobj-paramater | 関数のパラメーター |
組込みのoperatorは少ないですが、自分で追加することもできます。
operatorを新しく追加するには、operator-userを使います。
こちらも有用なものを列挙しておきます。
使用にはoperator-userが必要です。
text-object | 動作 |
---|---|
operator-replace | レジスタの内容でテキストを置き換える |
operator-camelize | snake_caseとCamelCaseを相互変換する |
operator-sequence | 複数のoperatorを連続して使用する |
operator-html-escape | HTML要素をエスケープする |
operator-comment | ソースコードをコメント・コメントアウトする |
operator-sort | テキストをソートする |
operator-commentとoperator-sortは私が作ったプラグインです。
後に動作を詳しく説明します。
これは、ソースコードをコメント・コメントアウトするoperatorです。
.vimrcに定義していましたが、長くなったのでプラグイン化しました。
コメント文字列は'commentstring'
を見ているのでカスタマイズしたい場合は適宜設定して下さい。
マッピングはarpeggioを使って、o
と任意のキーの同時押しにマッピングしています。
operatorのマッピングは2回繰り返して押すと行に対する動作になるので、シングルストロークのマッピングの方が便利です。
しかし、キーはなかなか空いてないので、同時押しにマッピングできるarpeggioを使っています。
Arpeggio map oc <Plug>(operator-comment) Arpeggio map od <Plug>(operator-uncomment)
textobj-indentがインストールされていると仮定して、動作は以下のようになります。
この状態で、o
とc
を同時押ししてtextobj-indentのマッピングii
を打鍵すると、
このようにインデントに沿ってコメント化されます。
複数行コメントの構文にも対応していますが、'commentstring'
は1つしか設定できないので使い分けることはできません。
また、コメントアウトされた状態で、<operator-uncomment>
を実行すると元のコメントアウトされていない状態に戻ります。
これはソートを実行するoperatorです。
こちらもプラグインとして公開しています。
operator-userはコマンドからoperatorを作ることができるので:sort
を使って以下のように定義することもできます。
call operator#user#define_ex_command('sort', 'sort')
しかし、行に対してしか動作しないので、文字列に対しても動作するようにプラグインを作りました。
行に対するソートは内部で:sort
を呼び出しているので、動作は:sort
と同じてす。
マッピングは以下のようにしています。
nmap [Space]S <Plug>(operator-sort)$ nmap [Space]s <Plug>(operator-sort) vmap [Space]s <Plug>(operator-sort)
文字列に対する動作は以下のようになります。
ウィンドウの下の部分がoperatorを実行した結果です。
ここではカーソル位置で[Space]sir,
と打鍵しています。
ir
は[]括弧の内部を示すtext-objectです。
,
は区切り文字で、文字列に対して実行する場合は区切りが自明ではないので入力する必要があります。
これは単にクリップボードにyankするだけのoperatorです。
noremap "+y
で十分だと思われるかもしれませんが、2回繰り返して押した時に正しく動かないので定義しています。
function! OperatorYankClipboard(motion_wiseness) let visual_commnad = \ operator#user#visual_command_from_wise_name(a:motion_wiseness) execute 'normal!' '`['.visual_commnad.'`]"+y' endfunction call operator#user#define('yank-clipboard', 'OperatorYankClipboard') Arpeggio map oy <Plug>(operator-yank-clipboard) Arpeggio map oy <Plug>(operator-yank-clipboard)
これはGoogle翻訳を使って英日翻訳をするoperatorです。
動作にはwebapi-vimが必要になります。
単にechoするだけのシンプルなものですが、ちょっとした英文を訳したい時に便利です。
function! OperatorTranslate(motion_wiseness) let visual_commnad = \ operator#user#visual_command_from_wise_name(a:motion_wiseness) let query = join(s:get_region("'[", "']", visual_commnad), "\n") let api = 'http://translate.google.com/translate_a/t' let response = http#get(api, { \ 'client': 'o', \ 'hl': 'en', \ 'sl': 'en', \ 'tl': 'ja', \ 'text': query \ }, {'User-Agent': 'Mozilla/5.0'}) if response.header[0] ==# 'HTTP/1.1 200 OK' let result = json#decode(response.content) echo join(map(result.sentences, 'v:val.trans')) else echoerr response.header[0] end endfunction call operator#user#define('translate', 'OperatorTranslate') Arpeggio map ot <Plug>(operator-translate)
このoperatorはs:get_region()
というヘルパー関数を使っています。
このヘルパー関数はgetpos()
で取る引数を2つと、visualmode()
で得られる値を取ります。
動作としてはgetpos()
を2回、それぞれの引数で呼んで、2つの座標間のテキストをgetline()
で取得して整形しています。
Visual modeで選択してyankすることでも取得できますが、このヘルパー関数はregisterを汚染しないのとカーソルの移動がないのが利点です。
以下にs:get_region()
の定義も載せます。
function! s:get_region(expr1, expr2, visual_commnad) let [lnum1, col1] = getpos(a:expr1)[1:2] let [lnum2, col2] = getpos(a:expr2)[1:2] let region = getline(lnum1, lnum2) if a:visual_commnad ==# "v" " char if lnum1 == lnum2 " single line let region[0] = s:strpart(region[-1], col1 - 1, col2 - (col1 - 1)) else " multi line let region[0] = s:strpart(region[0], col1 - 1) let region[-1] = s:strpart(region[-1], 0, col2) endif elseif a:visual_commnad ==# "V" " line let region += [''] else " block call map(region, 's:strpart(v:val, col1 - 1, col2 - (col1 - 1))') endif return region endfunction
また、末尾に日本語が来た時のために以下の関数も必要です。
function! s:strpart(src, start, ...) let str = strpart(a:src, a:start) if a:0 > 0 let i = byteidx(strpart(str, a:1 - 1), 1) - 1 return i == -1 ? str : strpart(str, 0, a:1 + i) else return str endif endfunction
これは渡された範囲の文字列で検索を実行するoperatorです。
これもs:get_region()
を使ってます。
カーソル下の単語で検索する*
と#
のoperator版と言えます。
私はVisual modeで使えれば十分なので:vmap
のみしています。
function! OperatorSearch(motion_wiseness) let visual_commnad = \ operator#user#visual_command_from_wise_name(a:motion_wiseness) let search_command = v:searchforward ? 'n' : 'N' let region = join(map(s:get_region("'[", "']", visual_commnad), \ 'escape(v:val, "\\/")'), \ '\n') let @/ = '\V' . region call histadd('/', '\V' . region) silent execute 'normal!' search_command endfunction call operator#user#define('search-forward', 'OperatorSearch', \ 'let v:searchforward = 1') call operator#user#define('search-backward', 'OperatorSearch', \ 'let v:searchforward = 0') vmap * <Plug>(operator-search-forward) vmap # <Plug>(operator-search-backward)
operatorは使いこなすと非常に便利です。
みなさんもoperatorを使いこなして快適なVim lifeを送りましょう。