Hachioji.pm #9 エアLT #hachiojipm

新潟から参加してます

新潟市民がHachioji.pmに(しかもエアLTで)参加するってどういうこと?という声が聴こえてきそうですが、そんなの知ったこっちゃない。Hachioji.pmとはゼーガペインでつながっているのだ(お彼岸セール許せないですね)。

今回のテーマ

今回のLTのテーマは「フリー」だとおききしております。というわけで、ふたつほどネタを投下します。

1)来週、プレNiigata.pmやります

以前このblogにも書きましたが、プレNiigata.pmを来週やります。 http://atnd.org/events/19693

新潟生活を始めて2週間。まだどこにどんな店があるのかわかってないのですが、とにかくごはんがおいしいです。タレかつ最高。Hachioji.pmのみなさんも、機会があれば是非Niigata.pmに遊びにきてください。素敵なごはんが待ってますよ!同居人にも訊かないとですが、多分、2人くらいならうちに泊まっていただいてもOKです。

2)分散ストレージの話

今仕事で関わっているサービスが、ストレージにAmazon S3を使おうとしているのですが、Amazon S3って転送量で従量課金されてしまうんですね。で、これがバカにできないレベルのお金になってくるので、以下のような工夫を考えました。

前提となっている状況として、

  • ユーザにアップロードされたばかりの新しいファイルへのアクセスは多い
  • 一方、古いファイルへのアクセスは急激に減る
  • ただし、ほとんどアクセスのないようなファイルも全て保存しておく必要がある。

という状況です。

というわけで、ユーザがファイルをアップロードしてきた段階で、S3へ保存するのと同時に、自社で持つキャッシュサーバ的なものにキャッシュします。キャッシュが存在する間はキャッシュからコンテンツを返し、キャッシュが存在しなくなったらS3に取りにいく、というような運用を考えました。

on readでキャッシュしてしまうと、新しいファイル1つにつき最低でも一回はS3から転送しないといけない(キャッシュする為にS3に読みにいく必要がある)のに対し、on writeでキャッシュすればキャッシュが存在する間はS3からの転送をしなくて済みます。S3からの転送が必要になるのは、キャッシュに存在しないような古いデータをリクエストされた時だけ(そしてそのようなリクエストは非常に少ない)で済むので、非常にお得です、というわけですね。

で、このような処理を毎回書くのはだるいので、store()やget()を呼ぶとキャッシュとか保存とかいろいろ透過的にやってくれる以下のような構造を持つライブラリを作りました。

こうすることで、ストレージをAmazonS3じゃなくて自社で持つ分散オブジェクトストレージに変えたい!とかキャッシュサーバを変えたい!とか言うときにCacheEngineやSotreEngineをぽこぽこと入れ替えるだけで対応できて便利ですね!

ところで、バイナリデータのキャッシュにMemcachedがあまりよろしくなくて、現在むりやりDiskにキャッシュしてるのですが、なんかいいキャッシュサーバ知りませんか?

私のエアLTは以上です。それではみなさん、YAPCでお会いしましょう!!!!!!!!!1111111111

舞浜サーバーリセットtweetスクリプト書きました

舞浜サーバーのリセットまで、あと一ヶ月を切りましたね。なんのことかわからないというあなたは、とりあえずゼーガペインを見てください。花澤香菜さん好きとか言ってるのにゼーガ見てないとか許されないですよ。

https://gist.github.com/1129201

これを走らせると、舞浜サーバーリセットまでの時間をTwitterにツイートします。どうぞご利用ください。(だれも使わなそう)

Niigata.pmを作りたい、という話

思いのたけを書いていたら長くなってしまったので、最初に結論だけ書いてあります。めんどいひとは最初だけ読むだけでいいです。

最初に結論

9月中旬に新潟県新潟市に引っ越すので、Niigata.pmを立ち上げたいです。仲間を大募集します。[twitter:@shinpei_cmyk]に一声かけてください。できれば9月中、遅くとも10月中にはプレNiigata.pmとして決起集会(という名の飲み会)を開き、その後勉強会やハッカソンなど、定期的な活動を行いたいと思っています。

地方格差は存在する

現在ぼくは東京でPerl(とかその他)プログラマ、運用エンジニアとして働いているのだけれど、一身上の都合で9月中旬に新潟県新潟市に引っ越しをすることになった。今後は新潟でPerl(とかその他)を書いたりサーバの面倒をみたりして働いていく。

ところで、ぼくは幼少〜高校時代を新潟県長岡市で過ごして、大学進学を機に東京に出てきた。その時の衝撃は、多分地方出身のひとにしかわからないのではないかなぁ、と思う。ひとことで言えば、「東京には、ひとと情報が集まっている。ずるい」。

ぼくはずっと音楽(やるのも聴くのも)を趣味にしていたのだけど、長岡市にいるときには同じようなレコードを聴いてるひとと一緒に遊んだりすることは本当にむずかしかった。情報交換だってなかなかできない(から、ミュージックマガジンさんにはほんとうにお世話になりました)。でも大学で東京に出てきたとたんに、音楽を通じた友達がおもしろいくらいにできた。ほんとうに、地方と東京では、驚くくらいに情報や人間の密度が違う。

情報はフラットになりつつある

ただ、インターネットの登場で、情報の格差というのは少しずつ埋まってきているようにも思う。地方にいたって、ちゃんとアンテナを張っていればネットでいくらでも情報は手に入る。勉強会にしたって、多くのひとたちが情報をOpen&Shareしてくれているおかげで(本当に本当にすばらしいことだと思う)、その勉強会でどんな発表がなされたのか知ることは簡単にわかるようになっているし、そういう感じで地方にいても情報を集めることは簡単にできるようになっている。

「スライド読めばそれでいいや」そんな風に考えていた時期がぼくにもありました

だから、東京で毎週のようにどこかで開催されている勉強会に参加しなくても、情報にキャッチアップしていくことってのは実はそんなに難しいことじゃない。情報という観点だけで見れば、勉強会に参加するメリットってそんなにないのかもしれない。正直な話、ぼくはちょっと前まで「勉強会って時間もかかるし、あとでまとめられた情報を読んだ方が効率よくない?」とか思っていた。

でも、初めてYokohama.pmトークをさせていただいたときに、その考え方は吹っ飛んだ。正確に言えば、懇親会に参加したときに、吹っ飛んだ。それは大学に出てきたときに受けたのと同種の衝撃だった。勉強会には、当然「その筋」のひとたちが集まっている。そこで、直接お話をさせていただくと、「情報を集める」というやりかたではみえないこと、できないことがたくさんあるということに気づく。Yokohama.pmの懇親会では「あの」id:kazeburoさんやid:sfujiwaraさんが、ぼくの開発したサービスのアーキテクチャに直接アドバイスをくださった。帰りの電車では、 「あの」[twitter:@tomita]さんがぼくの開発したサービスに興味を持ってくださった(なんか今ぼくすごくミーハーな人みたくなってるけど)。そういった方達だけではなく、勉強会には「同じことに興味を持っているひと」が集まってるから「すごく生な運用事例の話」とかも訊けるし、何より楽しい。こういうのは、「モニターの向こうの勉強会」では決して感じることができない。そんなこんなで、Yokohama.pmで喋った日の夜、「自分、非コミュなんで」と言ってそういう場に行くことを避けていた自分をぶん殴りたくなった。その後Hachioji.pmなどにも参加させていただいて、技術者のコミュニティというものの魅力に最近はめろめろになってしまっている。でも、同時に、こういう経験ができるのは、「自分が東京に住んでいる」という幸運に負うところが大きいということを、地方出身者としてずっと感じ続けてもいた。

つまり、情報はインターネットによってだいぶ格差がなくなってきているけれど、「同じことに興味を持っている人の集まる場所にいく」という経験は、決してインターネット(だけ)ではできない。そして、残念なことに、開催されている勉強会の数には、依然として地方格差が存在している(人口密度的にしょうがないんだけどさ)。

無いなら作ればいいじゃん!

という中でぼくは、まあいろんな都合で新潟市に引っ越しをすることになるんだけど、東京ほど人が集まってはいない新潟(周辺)にもPerlを書いているひとはいるはず。たとえば[twitter:@hayajo]さんとか(いきなりお名前出してすみません…)。だったら、Niigata.pm作ればいいんじゃないか?と思い立ったわけです。はっきり言って、ぼくはたいしたエンジニアじゃない。コードでコミュニティに貢献する、みたいなのはまだ(いつか!CPANの恩恵に恩返ししたいと思っているのだけれど)ちょっと力不足なのだけれど、直接コードで貢献しなくても、生の運用事例や生の情報を交換する場を作る、それでも少しはコミュニティに対する貢献になるんじゃないか。そんなふうに思っています。

最後にもう一度募集

というわけで、9月中旬に新潟県新潟市に引っ越すので、Niigata.pmを立ち上げたいです。仲間を大募集します。[twitter:@shinpei_cmyk]に一声かけてください。できれば9月中、遅くとも10月中にはプレNiigata.pmとして決起集会(という名の飲み会)を開き、その後勉強会やハッカソンなど、定期的な活動を行いたいと思っています。

言い残したこととか

Hachioji.pmの面々にはとてもよくしていただいていて、そこで出会えたひとたちにはいろいろなモチベーションをいただきましたし、現在進行形でいただいています。Hachioji.pmに参加していなかったらNiigata.pmを「やりたい」とは思っていても「やろう!」とまでは思えなかったと思います。とくに、[twitter:@maka2_donzoko]さんとはなんと同郷であることがわかり、「Niigata.pmやるなら、参加するよ」という力強いお言葉をいただけました。「のめしこき」のぼくも、Niigata.pmに関してはのめしをこかずにやっていきたい!と思っているので、新潟周辺在住でPerl(周辺)に興味がある方々、是非是非 join us!!!!!!!

Shibuya Perl Mongersテクニカルトーク#16で配られたうちわの解説

やっておりますShibuya.pm。今回のテーマは「夏の正規表現祭り」。会場はmixiさんでした。会場に着き、席に着くとmixiさんのノベルティがずらり。その中にmixiさん特製のうちわがあり、そこには、「ん?なんだかPerlっぽいぞ」という感じのコードが書かれていました。話によると、「夏の正規表現祭りだから正規表現っぽいコードをプリントしてあります」とのこと。

$_ = q.
socierviceal
ocialg
service
.;

s.ocial.networkin.g;s.ervice

..mixi;

print;

ためしにその場でエディタに打ち込んで実行してみると、以下のような文字列が出力されました。

social
networking
service

おおー。というわけで、このコード、上から読んでみました。式をひとつひとつ追っていくとそんなに変なことはやっていないのがわかると思います。

$_ = q.
socierviceal
ocialg
service
.;

はい、これは簡単ですね。perlでは q/string/ は 'string' と等価です。変数展開したい場合は qq/$string/ でおk。この、文字列を作る際に使うqとかqqは区切り文字(?)に任意の文字いろんな区切り文字を使えるので、 q/hogheoge/ は q{hogehoge} と書くこともできます。また、q astringaのようにqのあとに一文字スペースを入れると、区切り文字に任意の文字を使えます。というわけで、ひとつめの式は、 q.. で囲まれた文字列を特殊変数$_に代入していることになります。(はてブで間違いを指摘してもらいました。id:kitsさんありがとうございます)

で、次。

s.ocial.networkin.g;s.ervice

..mixi;

少し読みにくいので、整形します。

s.ocial.networkin.g;

s.ervice

..mixi;

ピン!と来てる人も多いと思いますが、さきほどはq//のトリックを使っていますが、今度はs///のトリックを使っています。置換に使う正規表現のs///も、文字列生成に使うq//と同じく、区切り文字(?)に任意の文字を使うことができます。というわけで、冒頭のコードを読みやすく整形したコードを下記に示します。

#文字列を作る
$_ = q/
socierviceal
ocialg
service
/; 

# $_の中の「ocial」を全て「networking」に置き換え(gオプション)
s/ocial/networkin/g; 

# 文字列を複数行として扱い(m)拡張正規表現を使用し(x)大文字小文字を無視して(i)、
# 「ervice」を一度だけ(gなし)空文字列に書き換える
s/ervice

//mixi;

print;

ということでした。おしまい。

Net::Twitter::UserStreamsというモジュールを書きました

cpan形式でモジュールを作る練習として、Net::Twitter::UserStreamsというモジュールを書きました。

このモジュールは、miyagawaさんのAnyEvent::Twitter::Streamの、UserStreamsを扱うことに特化した薄いラッパーです。思想としては、

  • AnyEventの層を隠蔽する
  • ネットワークの層も隠蔽する
  • とにかく手軽にUserStreamsをトラッキングできる

という思想で作りました。そのため、モジュール内で$cv->recvしているしモジュール内で無限ループしているという変な実装になっています。なので、「挙動を自分できちんと握りたい」という向きにはオススメできない感じです。一方、AnyEventとかPerlとかよくわかんないんだけど、たとえばフォセッタ (@Fossetta_Tokyo) | Twitterみたいなbotを作りたい、なんて時に手軽に使ってもらえるといいんじゃないかと思います。

ソースはgithubにあがっています。https://github.com/Shinpeim/p5-Net-Twitter-UserStreams

添削されると喜びます。

とりあえず使ってみたい方は、 cpanm https://github.com/Shinpeim/p5-Net-Twitter-UserStreams/tarball/master でどうぞ。

Schwartz変換 on Haskell

リストのリストを受け取って、リストの長さでソートするsortByLengthを書いてみる。

まずは素直に書いてみる。

import List;

sortByLength :: [[a]] -> [[a]]
sortByLength xs = sortBy cmp xs

cmp :: [a] -> [a] -> Ordering
cmp x y | length x < length y = LT
        | length x == length y = EQ
        | otherwise = GT

sortByを使うためにListをimportしている。SortByの型は(a -> a -> Ordering) -> [a] -> [a]。つまり、第一引き数に(a -> a -> Ordering)な型の関数、第二引き数に[a]をとり、[a]を返す関数。ここでは第一引き数に関数cmp,第二引き数にxsを渡している。

cmpは、[a],[a]を取りOrdering型を返す関数。ここでは二つの引き数(List)の長さを比べている。

問題点

cmpのパターンマッチングの中で何度もlengthを呼び出している。cmpはソート中に二値を比較するたびに適応されるので、ここで何度もlengthを呼び出すのはCPU効率が悪い(逆にShwartz変換はメモリ空間を犠牲にしているので、適材適所で使うべきだろうけど)。というわけで、Shwartz変換を使ってみます。

Shwartz変換バージョン

import List;

sortByLength xs = map untupplize $ sortBy cmp $ map tupplize xs

tupplize :: [a] -> ([a],Int)
tupplize xs = (xs, length xs)

untupplize :: ([a],Int) -> [a]
untupplize (xs,_) = xs;

cmp :: ([a], Int) -> ([a], Int) -> Ordering
cmp (_,x) (_,y) | x < y = LT
                | x == y = EQ
                | otherwise = GT

まず、tupplizeはリストを与えると(リスト,リストの長さ)という形のタプルを返す関数。これをmapに適応して、[リスト,リスト,リスト..]というリストを[(リスト,長さ),(リスト,長さ)..]というリストに変換する。

そのリストに対してcmpを適応する。cmpではパターンマッチで(リスト,長さ)から長さの部分をx,yという変数に束縛して、cmpではx,yの長さをチェックするだけ。毎回lengthを呼ばなくなったので効率が良くなった。

あとは、tupplizeされた状態の各要素をuntupplizeしてもとにもどしてあげれば良い。これはtupplizeの逆をやってるだけのなので割愛。

スコープを短く

tupplizeもuntupplizeもcmpも、ソートのためにしか使わないのだから、その中だけのスコープに限定したい

import List
sortByLength xs = map untupplize $ sortBy cmp $ map tupplize xs
    where
      tupplize xs = (xs, length xs)
      cmp (_,x) (_,y) | x < y = LT
                      | x == y = EQ
                      | otherwise = GT
      untupplize (xs,_) = xs

名前つけるまでもないものにはλ使う

tupplizeとuntupplize程度の処理、わざわざ名前付き関数にする必要もなし

import List
sortByLength xs = map (\(xs,_) -> xs)
                  $ sortBy cmp
                        $ map (\x -> (xs, length xs)) xs
    where
      cmp (_,x) (_,y) | x < y = LT
                      | x == y = EQ
                      | otherwise = GT

lambdaにしたら素直に後ろから読んでいくだけでわかりやすいコードになった。やったねたえちゃん!

関数合成してみる

sortByLengthの引き数がそのままmapの引き数になっててださいので、ポイントフリースタイルで書き直してみる

import List;

sortByLength :: [[a]] -> [[a]]
sortByLength  = map (\(xs,_) -> xs) . sortBy cmp . map (\xs -> (xs, length xs))
    where
      cmp (_,x) (_,y) | x < y = LT
                      | x == y = EQ
                      | otherwise = GT

Haskellでは . で関数を繋ぐことで関数合成ができる。g(f(x))が、g . fで表すことができる、という感じ。

このあたりは結構新しい概念なのでていねいに追う。

まずは、

map (\(xs,_) -> xs) . sortBy cmp . map (\xs -> (xs, length xs))

を詳細に観ていく。評価される優先順位がわかりやすい感じに書きかえる

(map (\(xs,_) -> xs)) . (sortBy cmp) . (map (\xs -> (xs, length xs)))

つまり、(map (\(xs,_) -> xs))で生まれる関数と(sortBy cmp)で生まれる関数と(map (\xs -> (xs, length xs)))で生まれる関数を合成している。

まずは一番最初 (map (\(xs,_) -> xs)) を見る。mapの型は (a -> b) -> [a] -> [b]で、ここでは引き数にラムダ式をひとつ与えている。これによって、mapがカリー化されるので、(map (\(xs,_) -> xs))が返すのは、型が[a] -> [b]な関数となる。

つぎに(sortBy comp)を見る。先ほどの例のおなじように考えて、sortBy の型が (a -> a -> Bool) -> [a] -> [a]なので、(sortBy cmp)は [a] -> [a]な型の関数となる。

最後のmapも同様。

これらのみっつの関数を合成したものを、sortByLengthに束縛している。

今日はここまで。

Haskell始めました

以前少しだけ触ったことがあってモナドで挫折していたHaskell。最近自分のスキルがあがっていない感を感じていて危機感を覚えたので、もう一度入門し直してみようと思って。学習の記録を残します。

小さいことからコツコツと書いていきます。

とりあえず簡単なリストについて

  • リストは[a,b,c]で作れる
[1,2,3,4,5]
["hello","haskell"]
  • 文字列の実態は文字のリスト
['h','e','l','l','o'] --"hello"
  • リストの中に入れられるのは同じ型のものだけ
[1,2,3,4,5] --OK
[1,'a',"hello"] --Error
  • Lispにおけるcar cdrは head tail
head [1,2,3,4,5] -- 1
tail [1,2,3,4,5] --[2,3,4,5]
  • Lispにおけるconsは 「:」
1 : [2,3,4,5] -- [1,2,3,4,5]

関数について

  • 書式
--test.hs
square n = n * n
  • 型を明示的に指定することもできる
--test.hs
square :: Int -> Int  -- squareという関数は、Intを引数にとり、Intを返す
square n = n * n  -- 関数定義の実体
  • 引数でパターンマッチもできる
--test.hs
times :: Int -> String -> [String]  -- repeat という関数は、Intを第一引数、Stringを第二引数にとり、Stringのリストを返す
times 0 str = []
times n str = str : times (n - 1) str
-- in REPL
> :l test.hs
> times 2 "hoge" --["hoge","hoge"]
  • 型変数で、「どんな型でもいいよ」を表すこともできる
times :: Int -> a -> [a]  -- times という関数は、Intを第一引数、何らかの型を第二引数にとり、そのリストを返す
times 0 x = []
times n x = x : times (n - 1) x
-- in REPL
> :l test.hs
> times 2 "hoge" --["hoge","hoge"]
> times 2 100 --[100,100]
> times 2 ["word","list"] --[["word","list"],["word","list"]]
  • バッククオートで囲むと 引数 `関数` 引数という書き方ができる
--in REPL
> :l test.hs
> 4 `times` "hey!" -- ["hey!","hey!","hey!","hey!"]
  • たとえば引き数をふたつとる関数を引き数ひとつで適応すると、カリー化された関数が返される
times :: Int -> a -> [a]
times 0 x = []
times n x = x : times (n - 1) x

twice = times 2
thrice = times 3
--in REPL
> :l test.hs
> twice "hey!" -- ["hey!","hey!"]
> thrice "hey!" -- ["hey!","hey!","hey!"]


とりあえず今日はここまで。このあたりまではとくにつまづくポイントもない。