PythonのfileinputをGoで実装してみた

Pythonの○○をGoで実装してみたシリーズ

fileinputは、行単位で処理を行うコマンド(特にUNIXフィルタ)を作るときなどに便利なライブラリ。

import fileinput
for line in fileinput.input():
    process(line)
  • コマンドライン引数で与えられたファイルの行に順番にアクセス
  • 引数が空の場合は標準出力の行にアクセス
  • ファイル名として '-' が与えられたときも標準出力の行にアクセス

github.com

使用例

sc := fileinput.Lines(os.Args[1:])
for sc.Scan() {
    process(sc.Text()) // 行を処理
}
if sc.Err() != nil {
    os.Stderr.WriteString(sc.Err().Error() + "\n")
    os.Exit(1)
}

// なお、ファイルハンドラを閉じる処理は.Scan()や.Err()の中で自動で行われる

Python版との違い

Go版はbufio.Scannerを真似たインターフェースにしました。 そのため、Pythonほどシンプルではありません(Pythonはfor文に渡すだけで使えるが、Go版は覚えるべきメソッドが3つもある)。

行をchan stringで返せば、Python版に近い書き心地になるような気もしましたが、 標準に合わせたいことと、チャネルを返そうとするとGoroutineを1つ余分に消費してしまうことがあり、 あきらめました。

#リハビリを兼ねて、チョチョイと書くつもりが、なぜか半日もかかってしまった・・・

Dockerの6つのtips

1. イメージを作る方法はDockerfileだけじゃない

Packerもある。

Packer by HashiCorp

Packerは様々な仮想環境のイメージを統一的な方法で作れるツールで、VirtualboxAWS・DigitalOceanなどにも対応しています。 したがって、DigitalOceanイメージとDockerイメージを1つのソースから作るといったことができます。

PackerはDockerの全機能が使えるわけではない(差分ビルドができない)のですが、 場合によっては問題にならないかもしれません。

2.Dockerイメージはtarで保存できる

Dockerイメージをtarとして保存すれば、Jenkinsの成果物として保存したり、S3経由で配信したりできます。

tar出力するにはdocker exportを使います。また、packerはデフォルトではイメージをtarで出力します。

3. 出力ファイルをchownする

docker run-v を付けると、コンテナにホストのディレクトリをマウントできます。

ところで、一般ユーザーがdocker runを実行しても、コンテナ内部ではrootユーザーとしてコマンドが実行されているため、出力ファイルもオーナー= rootで作られます。 このとき、ホスト側のディレクトリでもファイルのオーナーがrootになってしまうため、ファイルにアクセスできなくなってしまいます。

環境変数としてユーザー名を渡し、出力ファイルのオーナーを変更するとよいと思います。

docker-test.sh

#!/bin/bash
go test -v ./... | tee /docker/output/gotest.log
EXIT_CODE=$?

useradd "${OUTPUT_USER}"
chown "${OUTPUT_USER}:${OUTPUT_USER}" /docker/output/gotest.log

exit ${EXIT_CODE}
docker run \
  --env=OUTPUT_USER="${USER}" \
   -v "/workspace:/docker" \
  centos \
  docker-test.sh

4. --rmでコンテナを削除する

普通にdocker runするとコマンド終了後もコンテナが残り、いずれこのコンテナが溜まってディスクが溢れてしまいます。

docker run--rmをつけると、コマンド成功時には自動でコンテナを削除します。

5. cidfileで失敗時にもコンテナを削除する

docker runはコマンドが失敗したときにはコンテナを終了しません。 そのため、--rmをつけていてもコンテナが残ってしまいます。

docker run--cidfile=<file>をつけるとにコンテナのIDを出力するので、後からdocker stopします。

docker run --rm --cidfile=dockercid centos /bin/false

if [ -e dockercid ]; then
  CID=$(cat dockercid | cut -b -12)
  (docker ps -aq | grep "${CID}") && docker stop "${CID}"
  rm -f dockercid
fi

6. 古いコンテナ・イメージを削除する

さらに定期的に以下のコマンドを実行すれば、さらに安心です。

for cid in $(docker ps -aq); do
  docker rm "${cid}"
done

for iid in $(docker images | awk '/^<none>/ { print $3 }'); do
  docker rmi "${iid}"
done

まとめ

DockerはWordのイルカより有能です。

メモ:ローカル環境でCookieが動かないなら、127.0.0.1を疑え。

Cookieを使った認証において、認証取得先とリダイレクト先などは同じ表記でなくてはならない。

すなわち、localhost127.0.0.1は異なるホストとみなされる。設定ではどちらかに統一すること。

VMWare fusion上のXubuntuで?GVimが正しく描画されない問題

GVimでスクロールしたり、ggでジャンプしたりすると正しく再描画されず画面が真っ黒に。

(たちの悪いことに、マウスで少しスクロールすると再描画される。  そのため、絶妙な「不便で作業効率は落ちるけど一応使えてしまう」不具合になっている)

どうやら前からある問題のよう https://groups.google.com/forum/#!topic/vim_use/_goVuyxdgWA

解決方法としては、掲示板にもあるように、最新版を諦めてv7-3-367を使うこと。

ただし、これはVimではなくVMWare Fusion側の問題かもしれない。

もしプログラミング言語がお昼ごはんだったら

f:id:doloopwhile:20141220233843j:plain

ソフトウェアを開発するためのプログラミング言語には様々な種類があり、 歴史あるもの新しいもの、手軽さを重視したもの厳密さを追求したもの、など様々な種類があります。

ここでは、8種類のプログラミング言語を、それぞれお昼ごはんに例えてみました。 どれもとても美味しそうですね。

アセンブラ

f:id:doloopwhile:20141220233536j:plain

ウサギ。まず血抜きの処理から始まる。

C言語

f:id:doloopwhile:20141220233541j:plain

昔はこういうのを食べていた。

Python

f:id:doloopwhile:20141220233610j:plain

色とりどりのおかずは栄養バランスがよく、持ち運びにも便利です。

Java (Version 1.5)

f:id:doloopwhile:20141220233717j:plain

栄養バランスがよく、持ち運びにも便利ですが・・・

PHP

f:id:doloopwhile:20141220233731j:plain

ハンバーガーセット。たまに食べる分には手軽で旨い。でも毎日はちょっとね。

Go

f:id:doloopwhile:20141220233737j:plain

ステーキ定食。そうそう、こういうのでいいんだよ、こういうので。

Haskell

f:id:doloopwhile:20141220233746j:plain

マナーに厳しいおフランス料理店のフルコース。味は絶品だけど、フォークの上げ下げにも一々注意され、 メインディッシュを食べる前に追い出されました。

Common Lisp

f:id:doloopwhile:20141220233754j:plain

高級食材。いや、レ・ディシプル・ド・オーギュスト・エスコフィエのあなたにとっては最高の食材かもしれませんがね、 我々には調理する腕も時間も無いんです。今日のランチを食べたいだけなんです。

あとがき

ついカっとなってやりました。他言語の画像も募集中。

AngularJSのチュートリアルを移植して感じた、Elmの3つの良い点と不満点

これはElm Advent Calendar 2014の14日目の記事です。

AngularJSのチュートリアルをElmで再実装してみました!

doloopwhile/elm-phonecat · GitHub

まだ、本体のソースコードだけで、解説は何もないのですが・・・。

書いていて、気づいた事を、良い点・不満点それぞれ3つずつ書いていきます。

良い点1:必要な機能が十分に実装されている

Elm v0.14ではJson.Decoderが追加され、JSONのパースを実用的な記述量で実装できるようになりました。

私にとってはこれがAngularJSのチュートリアルをElmで再実装する上での「最後のピース」でした。

もちろん「ウチの業務アプリをElmで再実装してよ」と言われると「いやAngularJSの方が・・・」となります。まだElmには荒削りなところもあります。

しかし、「オモチャ」と言われる段階は脱したという印象です。

良い点2:直感的

Elmは「やりたいこと」と「解決法」がすぐ近くにある感じです。AngularJSのように新しいモジュールを勉強する回り道をしなくても、すぐに解決出来るのです。

例えば、AngularJS版のチュートリアルでは「True/Falseの代わりに"✓✗"を表示する」のために、Step-9を丸々ngFilterの説明に使っています。

一方Elmでは、

checkmark x = if x then plainText "\x2713" else plainText "\x2718"

の一行だけで用が足ります。

良い点3:エラー処理をサボれない

MaybeResultを使って、エラー処理を厳格に行えます。

JavaScriptだとNULLチェックなどは「if != null、if != null はぁ〜面倒くさいなあ」となってしまいがちですが、ElmではMaybeのチェックをしないとそもそもコンパイルが通らないので、ものぐさな私でも安全なコードを強制的に書くことになります。

まぁ、「厳格」はデメリットでもあります(JSONのフィールドを全列挙しないとDecoderが作れないとか)。

不満点1:Signalが不便(特に、ページの階層構造を作りづらい)

一方、ElmはまだSignalが使いづらいと感じました。

今回作ったのはシングルページアプリケーション(SPA)でしたが、AngularJSの場合は、

  • 実際にページを描画するTemplate/Controller
  • URLからTemplate/Controllerを選択するngRoute

という仕組みになっています。ngRoute側ではページの描画の仕方を知る必要はありません。 また、各ページのTemplate同士(例えば、トップページのTemplateと、詳細ページのTemplate)は互いの事を知る必要がありません。「関心の分離」ができる、ナイスな仕組みです。

しかし、残念ながら、現在のElmではngRouteの真似はできないようです。

-- 注:このコードは実際のElmではありません。

-- URLのハッシュによって、ページを切り替える関数
route : Location -> Signal Element
route location = 
  if | startsWith("#/phones", location.hash) -> phoneView location
     | otherwise -> indexView

-- トップページを描画
indexView : Signal Element
indexView =
  indexPageImpl <~ Time.fps 30 ~ Mouse.clicks

-- カタログの個別ページの描画
phoneView : Location -> Signal Element
phoneView location =
  let
    api = Http.sendGet ("/api/phones/" ++ String.dropLeft(8, location.hash))
  in
    phonePageImpl <~ Time.fps 30 ~ Mouse.position ~ api

-- さて、main関数の型は?
main = route <~ location

main関数はElement型かSignal Element型でなくてはならないからです。そのためmain関数は「全ページの描画とルーティングに必要な全部のSignalを<~ ~で連結する」ことになりがちです。

不満点2:union typeの処理が煩雑

ElmにはMaybeResultHttp.Responseなどのunion typeがあるおかげで、エラー処理を安全に行えるのですが・・・case文がネストしまくって大変です。

今回のチュートリアルでは、JSON APIのレスポンスを、

phonesResp : Http.Response (Result String (List Phone))

という型にしました。

実際にその値を処理している部分はこちら

case phonesResp of
  Http.Waiting ->
    Text.plainText "waiting"
  Http.Failure code text ->
    Text.plainText ("failed with " ++ (toString code) ++ " " ++ text)
  Http.Success resultPhones ->
    case resultPhones of
      Err error ->
        Text.plainText error
      Ok phones ->
        indexElements q sortPhones phones

JavaScriptだったら「早期リターンに書きなおすこと -1 f:id:doloopwhile:20141215023108p:plain」と、コードレビューで却下されても仕方ない感じのコードをElmでは書かざるをえません。

また、標準ライブラリのSignal Maybeモジュールも、「Signal (Maybe a)からNothingではない値を取り出して返すSignal a」のような関数が無いなど、細かい関数も不足しています。

悪い所3:レイアウト・スタイルライブラリの不足

ElmはHTML/CSSよりずっと直感的にレイアウト・スタイルを書けるのですが、やはりまだ機能不足です。

例えば、

  • CSSの"border"相当のものが無い(containerを2つ重ねて無理やりborderを表現することはできます)。
  • plainTextcontainerで囲んでも、中の文字列は改行されない(自動で改行させる方法がない?自分で改行位置で分割してText.Lineなどを使えば可能)。
  • 「画像を400x400に収まるように縦横比率を変えずにリサイズ」などができない(そもそも画像サイズを取得できない。Portを使えば無理やりできなくもないかもしれないが・・・)

一般的に、基本的なことはElmの方がAngularJSより簡単に出来るのですが、上に上げたような事を実装しようとするとElmでは不可能ではないものの多くの労力を割いて、細かいところまで実装しなくてはなりません。

「よくある処理」「80%のケースを20%の労力でカバーできるデフォルトの選択肢」がもっと用意されればいいな、と思いました。

f:id:doloopwhile:20141215024400j:plain

あとがき

Elm Advent Calendar 2014にはまだ十分な余裕がございます。ふるってご参加ください。