かとじゅんの技術日誌

技術の話をするところ

ページング用データに対応したFunctorを実装してみる

連続投稿ですが、すごいH本を読みながら、ふとアイデアが湧いてきたのでHaskellコードを書きなぐってみた。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

Functorは写像を作るための型クラス。写像とは単なるコピーではなく、何からの変換処理を意味するのですが、そのFunctorの題材としてなんかよいものないかなーと考えていたのですが、ページング処理でよく使う"ページ"をFunctorにすると便利じゃないか!と思ったので考えてみた。コードは次のとおり。

data Page a = EmptyPage |
              DefinedPage {
                    items :: [a],
                    pageNo :: Int,
                    offset :: Int,
                    total :: Int
              } deriving Show


prev :: Page a -> Maybe Int
prev EmptyPage = Nothing
prev (DefinedPage _ p _ _) = if (p -1 > -1)
                             then Just $ p - 1
                             else Nothing
next :: Page a -> Maybe Int
next EmptyPage = Nothing
next (DefinedPage i p o t) = if (o + length i < t)
                             then Just $ p + 1
                             else Nothing

instance Functor Page where
  fmap f EmptyPage = EmptyPage
  fmap f (DefinedPage i p o t) = DefinedPage {
                                    items = map f i,
                                    pageNo = p,
                                    offset = o,
                                    total = t
                                 }

main = do
        let defined = DefinedPage{ items = [1, 2, 3], pageNo = 1, offset = 0, total = 10 }
        let empty = EmptyPage
        let fmapPage1 = fmap (+1) defined
        let fmapPage2 = fmap (+1) empty
        putStrLn $ show(fmapPage1) ++ ", prev = " ++ show(prev fmapPage1) ++ ", next = " ++ show(next fmapPage1)
        putStrLn $ show(fmapPage2) ++ ", prev = " ++ show(prev fmapPage2) ++ ", next = " ++ show(next fmapPage2)

ざっと解説。 Pageは型引数を一つ取るページを表す独自データ型です。EmptyPageは空のページを表す。DefinedPageにはページ内のデータを表すitems, ページ番号のpageNo, オフセットのoffset, 全件数のtotalの属性を持っています。prev関数とnext関数はそのページの前後のページ番号を返します。

Page用のFunctor型クラスのインスタンス(実装)を定義。EmptyPageが来た時はEmptyPageを返し、DefinedPageの時は関数であるfをitemsにmapしその結果を保持する新たなDefinedPageを返します。

このFunctorインスタンスのおかげで、

*Main> fmap (+1) EmptyPage
EmptyPage

*Main> fmap (+1) DefinedPage { items = [1,2,3], pageNo = 1, offset = 1, total = 10 }
DefinedPage {items = [2,3,4], pageNo = 1, offset = 1, total = 10}

が可能になります。これは便利すぎる! というわけで、main部分の処理では、DefinedPageやEmptyPageを気にせずにfmapやってます。(mainの処理を関数に切り出したいな…)