最初のお題 CRUD - Haskell 版

 

文字通り、初めて Haskell のコードを書いてみました。

まずは、 ふつうのHaskellプログラミング を半分ほど読んで雰囲気を掴みました。 そのあとは「最初のお題 CRUD」を書き上げるのに必要なことを Google で検索しまくりました。

Haskell のコードを書くにあたって、次のようなことを考えました。

  • Haskell(関数型)らしくなくても良いから、とにかく動くものを書き上げる。
  • ループ構造は再帰で実現する。
  • 保存したいデータは、関数に渡して持ち回る。
  • ハッシュ(連想配列)はタプル(tuple)のリストで代用する。
  • 正規表現はパーサコンビネータ(Parsec)で代用する。
  • 例外処理(error, catch)は使わない。 使うのであれば、良く理解してからにすべし。

実行サンプル

Haskell 版の動作は次のようになります。 select コマンドは、スペックダウンして引数がなくなっています。 また、メッセージが英文になっています。

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 6.8.2
$ chmod +x crud.hs
$ ./crud.hs
CRUD> hello
Syntax error.
CRUD> insert "United Kingdom" => "London"
CRUD> insert "Japan" => "Osaka"
CRUD> select
United Kingdom => London
Japan => Osaka
CRUD> insert "Japan" => "Tokyo"
"Japan" already exists.
CRUD> update "Japan" => "Tokyo"
CRUD> select
United Kingdom => London
Japan => Tokyo
CRUD> delete "japan"
"japan" is not found.
CRUD> delete "Japan"
CRUD> select
United Kingdom => London
CRUD> exit
$ 

ソースコード

crud.hs
#!/usr/bin/runghc

import Char
import List
import Text.ParserCombinators.Parsec

strip :: String -> String
strip = reverse . dropWhile isSpace . reverse . dropWhile isSpace

data Cmd = Cmd { aCmd::String, aArg0::String, aArg1::String }

crudInsert tuple hash =                                     -- insert
    case (lookup (fst tuple) hash) of
        Just val -> Left (show (fst tuple) ++ " already exists.")
        Nothing  -> Right $ hash ++ [tuple]

crudSelect hash =                                           -- select
    putStr $ unlines $ map format hash
    where
        format t = (fst t) ++ " => " ++ (snd t)

crudUpdate tuple hash =                                     -- update
    case (lookup (fst tuple) hash) of
        Just val -> Right $ map update hash
        Nothing  -> Left (show (fst tuple) ++ " is not found.")
    where
        update t = if (fst t) == (fst tuple) then tuple else t

crudDelete key hash =                                       -- delete
    case (lookup key hash) of
        Just val -> Right $ delete (key, val) hash
        Nothing  -> Left (show key ++ " is not found.")

parseCmd :: Parser Cmd
parseCmd =
        try(do  eof;
                return (Cmd "" "" ""))                      -- CRUD>

    <|> try(do  cmd  <- many1 letter;
                eof;
                return (Cmd cmd "" ""))                     -- CRUD> cmd

    <|> try(do  cmd  <- many1 letter;
                spaces;
                char '"'; arg0 <- many1 (noneOf "\""); char '"';
                eof;
                return (Cmd cmd arg0 ""))                   -- CRUD> cmd "arg0"

    <|> try(do  cmd  <- many1 letter;
                spaces;
                char '"'; arg0 <- many1 (noneOf "\""); char '"';
                spaces; string "=>"; spaces;
                char '"'; arg1 <- many1 (noneOf "\""); char '"';
                eof;
                return (Cmd cmd arg0 arg1))                 -- CRUD> cmd "arg0" => "arg1"

    <|> fail ""

loop :: [(String, String)] -> IO ()
loop hash = do
    cs <- putStr "CRUD> " >> getLine
    case (parse parseCmd "" $ strip cs) of
        Right r -> do
            case (aCmd r) of
                "insert" | (aArg0 r)/="" && (aArg1 r)/=""
                            ->  case (crudInsert (aArg0 r, aArg1 r) hash) of
                                    Right   r -> loop r
                                    Left    e -> putStrLn e >> loop hash

                "select" | (aArg0 r)=="" && (aArg1 r)==""
                            ->  crudSelect hash >> loop hash

                "update" | (aArg0 r)/="" && (aArg1 r)/=""
                            ->  case (crudUpdate (aArg0 r, aArg1 r) hash) of
                                    Right   r -> loop r
                                    Left    e -> putStrLn e >> loop hash

                "delete" | (aArg0 r)/="" && (aArg1 r)==""
                            ->  case (crudDelete (aArg0 r) hash) of
                                    Right   r -> loop r
                                    Left    e -> putStrLn e >> loop hash

                "exit"   | (aArg0 r)=="" && (aArg1 r)==""
                            ->  return ()

                ""          -> loop hash

                otherwise   -> putStrLn "Syntax error." >> loop hash

        Left e -> putStrLn "Syntax error." >> loop hash

main = loop []

更新履歴

日付 内容
2008-04-05 初版