Golangのメソッドチェーンが長くなってしまう問題

gormなど、メソッドチェーンを多用できるライブラリを使っている時に、 どうしても一行が長くなってしまう問題がある。

実際のコード:

if err := db.Model(&User{}).Where(&User{Name: "hogehoge"}).Count(&cnt).Error; err != nil {
  panic(err)
}

本当はこう書きたい:

if err := db.Model(&User{})
    .Where(&User{Name: "hogehoge"})
    .Count(&cnt)
    .Error; err != nil {
  panic(err)
}

折角メソッドチェーンでシンプルにかけるようになっているのに勿体無い…。

少し調べてみたが、今のところ一時変数を使った解決策しか無いようだ。

c := db.Model(&User{})
c = c.Where(&User{Name: "hogehoge"})
c = c.Count(&cnt)
if c.Error != nil {
     panic(c.Error)
}

HBaseを支えるLSM-tree

最近HBaseについてちょくちょく勉強しているので、まとめていこうかと

今回は、HBaseのストレージ構造であるLSM-treeについて。

LSM-treeとは

RDBMSがストレージ構造としてB-Tree(特にB+Tree)を用いているのに対して、HBaseはLog-structured merge-tree(LSM-tree)を用いている。 従来のB+Treeがシークを多用するのに対して、LSM-treeはシークを最小限にするよう設計されている。

書き込み

HBaseに対して書き込み処理が行われると、最初にログファイル(HLog)対してシーケンシャルにデータ変更が書き込まれる。 万が一の場合、このログファイルから変更を復元することができる。 ログファイルへの書き込みが成功すると、インメモリのストアであるMemStoreに対して変更が書き込まれる。 MemStoreの内部はB-Treeなどでソートされており、直近の変更に対する高速な読み出しを実現している。

MemStoreが一杯になると、データがディスクにフラッシュされ、新たなストアファイルとして永続化される。 なお、MemStoreのフラッシュが完了すると、その分のログファイルは破棄される。

読み込み

特定のKeyに対しての読み込み操作が要求されると、まずMemStoreから探索が行われ、データがない場合はストアファイルを順に探索していく。 ストアファイルの先頭にはKeyとファイル内のデータの位置が記録されたMapがあり、それを用いることで1回のシークで目的のデータを探索することが可能である。 以上の構造から、ディスクシークは最大でもストアファイルの個数分に抑えられる。

コンパクション

書き込み量が増加するにつれて必然的にストアファイル数も増加し、結果としてディスクシークの回数が増えてしまう。 これを防ぐためにHBaseは定期的にストアファイルのコンパクションを行う。 コンパクションとは、複数のストアファイルをマージ&ソートする処理で、これによって小さく多量なストアファイルを大きく少量のストアファイルにマージし、ストアファイル数の増大を防ぐことができる。

まとめ

ぱっと見ただけでも、シークを最小限にする仕組みが随所にあり、おもしろい。 まだまだ理解していない所がたくさんあるので、馬本読み進めたい。

参考

HaskellでFunctional Reactive Programming(FRP)を試してみる

HaskellFRPをするためのライブラリ"Sodium"を少し触ってみました。

Sodiumは、C#, C++, Java, Scala, Haskellに対応していて、各言語間で類似したインターフェースを持つように設計されたFRPライブラリです。 FRPの実装方式は数種類あって、主にpull based、push basedが主流の2タイプのようなのですが、Sodiumはpush basedで実装されています。(push basedのほうが後発?)

EventとBehavior

FRPの基本であるEventとBehaviorについて軽く学んでおきましょう。

Eventとは時間と値が対になったデータのストリームです。例えば、マウスのクリックや、キーボードの入力、チャットの投稿の連続がこれにあたります。

Behaviorは時間によって変化するデータです。現在のマウスの位置や、チャットにいる現在の人数などがこれにあたります。時間に対しての連続性は保証されません。

基本的にこの2つの概念を用いてイベントドリブンで宣言的に手続きを記述できるのがFRPです。

Sodiumの基本

今回はEventのみ簡単に説明します。

Event

イベントの作成はnewEvent :: Reactive (Event a, a -> Reactive ())を使って行います。

listen :: Event a -> (a -> IO ()) -> Reactive (IO ())を用いることでイベントストリームに流れてきたイベントに対してActionを実行することができます。listenの返り値のIO ()を実行することによってストリームが破棄できます。

Reactive async :: Reactive a -> IO aで実行します。

以下の例では10秒間、1秒おきにtickと出力します。

import           Control.Concurrent (forkIO, threadDelay)
import           Control.Monad      (forever)
--
import           FRP.Sodium

main :: IO ()
main = do
  (event, push) <- sync newEvent
  unlisten <- sync $ listen event $ \_ -> putStrLn "tick"
  forkIO $ interval 1 push
  threadDelay 10000000
  unlisten

interval :: Int -> (() -> Reactive ()) -> IO ()
interval sec push = forever $ do
  sync $ push ()
  threadDelay $ 1000000 * sec

チャットサーバーを作る

では、シンプルなチャットサーバーを実装してみましょう。

module Main where

import           Control.Applicative ((<$>))
import           Control.Concurrent  (forkIO)
import           Control.Exception   (bracket)
import           Control.Monad       (forever, void)
import           Data.Monoid         (mconcat)
import           Network             (PortID (..), Socket, accept, listenOn,
                                      sClose)
import           System.IO           (Handle, hGetLine, hPutStrLn)
--
import           FRP.Sodium

data ChatData = Join String
              | Message String String
              deriving Show

main :: IO ()
main = bracket (listenOn $ PortNumber 9000) sClose chatServer

chatServer :: Socket -> IO ()
chatServer sock = do
  (chatEvent, chatPut) <- sync newEvent
  syncEvent $ serverLog chatEvent
  forever $ do
    (h, _, _) <- accept sock
    name <- init <$> hGetLine h
    sync $ chatPut $ Join name
    syncEvent $ listenChat h chatEvent
    forkIO $ forever $ hGetLine h >>= sync . chatPut . Message name

syncEvent :: Event (IO a) -> IO (IO ())
syncEvent = sync . flip listen void

serverLog :: Event ChatData -> Event (IO ())
serverLog = fmap print

listenChat :: Handle -> Event ChatData -> Event (IO ())
listenChat h ev = flip fmap ev  $ \chat ->
  hPutStrLn h $ case chat of
                 Join name -> mconcat ["User joined: ", name]
                 Message name mes -> mconcat [name, ": ", mes]

TCP Socketで複数人のチャットができるシンプルなサーバーです。

Event ChatDataが入室情報、メッセージが流れるストリームで、serverLogとlistenChatがそれぞれサーバー向け、クライアント向けにChatDataストリームをIO ()アクションストリームへと変換しています。

EventはFunctorなのでfmapを使ってIO ()へと移すことができます。

今回はシンプルなプログラムだったのでEventの変換は多用しませんでしたが、動作が複雑になってくるとEventの変換、2つのEventを1つにmergeしたりするなどしてリアクティブに記述していくのがいいのではと思います。

mergeやBehaviorなどについてはAPIドキュメントを参照。