株式会社インフィニットループ PHPとスマホアプリ開発を行う札幌のシステム会社

技術ブログ

  1. トップ>
  2. 技術ブログ>
  3. emonkakの記事一覧

2014年05月16日 (金)

著者 : 

シュキーンの開発とドメイン駆動設計について

こんにちわちわ。Underbar.phpの記事ぶりになりました@emonkakです。

本エントリでは、以前のエントリでお伝えした勤怠管理アプリケーションのシュキーンの開発について述べたいと思います。

シュキーンとは

シュキーンはAndroidで動作する勤怠管理アプリケーションです。打刻はNFCタグをAndroid端末にかざすことで行います。勤怠データはAndroid端末からサーバーに送信されるので、ネットワーク環境さえあればどこからでも確認することがきます。

nexus7-shukiin

(続きを読む…)

2013年08月01日 (木)

著者 : 

PHPのコレクション処理ライブラリUnderbar.phpの紹介

こんにちわちわ。
以前に便利なVimのOperatorをマスターするを書きましたemonkakです。

今回は私が個人的に開発を進めていて以前に社内勉強会で発表しました、PHPのコレクション処理ライブラリのUnderbar.phpの紹介をしたいと思います。

PHPのarrayなんとか関数は使い勝手が悪い

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を返すなんてことも当然できません。

そこでarrayTraversableなオブジェクトを同じように扱えて、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つあります。

  • 要素5つ分のメモリ空間で十分なのにIteratorの長さ分だけ空間を必要とする
  • 今回必要としない5個目以降の要素についても$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とは

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を使っていますが、他にArrayImplGeneratorImplがあります。

その他のPHPのコレクション処理ライブラリ達

PHPのarrayが扱いづらい問題はPHPerの共通見解のようでUnderbar.php以外にもいくつかコレクション処理ライブラリが開発されています。
大きくUnderscore.js系とLINQ系に分けることができます。

Underscore.js系

自分も1つ増やしてしまったわけですが、ちょっと乱立気味ですね。
本来こういった機能は言語の組み込みにするなり、何か統一を図るべきなのかもしれません。
まあPHPなのでしょうがないですね!

この中だと本家のUnderscore.jsが即時実行する実装なのもあって、遅延実行できる実装はありません。
これがUnderbar.phpの最初の開発の動機でした。

LINQ系

LINQ系のライブラリも結構数がありますが、最後発のGinqが設計が洗練されていて非常に良くできています。このスライドで大体の雰囲気を掴めるかと思います。

LINQ好きはGinq使っておけば間違いないですね。
Underbar.phpにはないjoinによるデータの結合やソートの合成などもあるのでおすすめです。

まとめ

PHPの配列を扱う関数は非常に使い勝手が悪いので、様々なコレクション処理のためのライブラリが開発されています。
しかし、中でも遅延リストを扱えるものは少ないので、そういった意味ではUnderbar.phpが選択肢の一つになりうると思います。

Underbar.phpは今後社内でも利用しようという動きがありますが、まだ誕生したばかりのライブラリなので不具合や要望等あればpull requestIssuesの方にお願いします。

PHPのコレクション処理ライブラリUnderbar.phpの紹介

最後に社内勉強会でのUnderbar.php発表の様子とスライドです。

2011年11月28日 (月)

著者 : 

便利なVimのOperatorをマスターする

こんにちわちわ、10月に新しく入りましたemonkakです。

今回は全国1000万人のVimmerのみなさんに、便利なVimのoperatorについて紹介したいと思います。
opepatorとはお馴染みy(yank)d(delete)などの、範囲を取るコマンドのことです。

operatorはカーソルを移動するweなどの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一覧

以下がデフォルトで定義されているoperatorです。
c d yとインデント関連以外はあまり使うことはないかもしれません。
ROT13のoperatorがあるのは面白いです。

マッピング 動作
c 変更する
d 削除する
y yankする
~ 大文字小文字を入れ替える (‘tildeop’がセットされていれば)
g~ 大文字小文字を入れ替える
gu 小文字にする
gU 大文字にする
! 外部プログラムでフィルターする
= インデントを整形する
gq テキストを整形する(幅を80に収めたり)
g? ROT13で暗号化する
> 右にインデントする
< 左にインデントする
zf foldを定義する
g@ ‘operatorfunc’に設定された関数を呼び出す

text-object一覧

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-userで新しくoperatorを追加する

組込みの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

operator-comment

これは、ソースコードをコメント・コメントアウトするoperatorです。
.vimrcに定義していましたが、長くなったのでプラグイン化しました。
コメント文字列は'commentstring'を見ているのでカスタマイズしたい場合は適宜設定して下さい。

マッピングはarpeggioを使って、oと任意のキーの同時押しにマッピングしています。
operatorのマッピングは2回繰り返して押すと行に対する動作になるので、シングルストロークのマッピングの方が便利です。
しかし、キーはなかなか空いてないので、同時押しにマッピングできるarpeggioを使っています。

	Arpeggio map oc  <Plug>(operator-comment)
	Arpeggio map od  <Plug>(operator-uncomment)
	

textobj-indentがインストールされていると仮定して、動作は以下のようになります。

Before

operator-comment-before

この状態で、ocを同時押ししてtextobj-indentのマッピングiiを打鍵すると、

After

operator-comment-after

このようにインデントに沿ってコメント化されます。
複数行コメントの構文にも対応していますが、'commentstring'は1つしか設定できないので使い分けることはできません。

また、コメントアウトされた状態で、<operator-uncomment>を実行すると元のコメントアウトされていない状態に戻ります。

operator-sort

これはソートを実行する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-sort

ウィンドウの下の部分がoperatorを実行した結果です。
ここではカーソル位置で[Space]sir,と打鍵しています。
irは[]括弧の内部を示すtext-objectです。
,は区切り文字で、文字列に対して実行する場合は区切りが自明ではないので入力する必要があります。

operator-yank-clipboard

これは単にクリップボードに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)
	

operator-translate

これは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を送りましょう。

  • このブログについて

    このブログは、札幌市の「株式会社インフィニットループ」が運営する技術ブログです。
    お仕事で使えるITネタを社員たちが発信します!

    最新の記事