fugafuga.write

日々のログ

すごいH本 part63

コマンドライン引数

コマンドライン引数をとれるようにする。

import System.Environment
import Data.List

main = do
    args <- getArgs
    progName <- getProgName
    putStrLn "The arguments are:"
    mapM putStrLn args
    putStrLn "The program name is:"
    putStrLn progName

ビルドして実行

> stack ghc -- --make arg-test.hs
[1 of 1] Compiling Main             ( arg-test.hs, arg-test.o )
Linking arg-test ...
> ./arg-test first second w00t "multi word arg"
The arguments are:
first
second
w00t
multi word arg
The program name is:
arg-test

Todoリスト改良する

  • タスク閲覧
  • タスク追加
  • タスク削除

できるようにする

コマンドと引数のリストを受け取って望みの動作を行うI/Oアクションを返す関数を作る。

import System.Environment
import System.Directory
import System.IO
import Data.List
import Control.Exception

dispatch :: String -> [String] -> IO ()
dispatch "add" = add
dispatch "view" = view
dispatch "remove" = remove

main = do
    (command:argList) <- getArgs
    dispatch command argList

add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")

view :: [String] -> IO ()
view [fileName] = do
    contents <- readFile fileName
    let todoTasks = lines contents
        numberedTasks = zipWith (\n line -> show n ++ " - " ++ line)
                        [0..] todoTasks
    putStr $ unlines numberedTasks

remove :: [String] -> IO ()
remove [fileName, numberString] = do
    contents <- readFile fileName
    let todoTasks = lines contents
        numberedTasks = zipWith (\n line -> show n ++ " - " ++ line)
                        [0..] todoTasks
    let number = read numberString
        newTodoItems = unlines $ delete (todoTasks !! number) todoTasks
    bracketOnError (openTempFile "." "temp")
        (\(tempName, tempHandle) -> do
            hClose tempHandle
            removeFile tempName)

        (\(tempName, tempHandle) -> do
            hPutStr tempHandle newTodoItems
            hClose tempHandle
            removeFile "todo.txt"
            renameFile tempName "todo.txt")

ビルド・実行

> stack ghc -- --make todo.hs
[1 of 1] Compiling Main             ( todo.hs, todo.o )
Linking todo ...
> ./todo view todo.txt
0 - 11111111111111
1 - chinchin
> ./todo add todo.txt "chinchinchin"
> ./todo view todo.txt
0 - 11111111111111
1 - chinchin
2 - chinchinchin
> ./todo remove todo.txt 0
> ./todo view todo.txt
0 - chinchin
1 - chinchinchin

dispatch関数を使うことで簡単に機能を追加することができる。

ファイルとタスクの番号を受け取り、リストの先頭に持ってくるbump関数を実装してみる

bump :: [String] -> IO ()
bump [fileName, numberString] = do
    contents <- readFile fileName
    let number = read numberString
        todoTasks = lines contents
        selectedTask = todoTasks !! number
        newTodoItems = unlines $ [selectedTask] ++ (delete selectedTask todoTasks)
    bracketOnError (openTempFile "." "temp")
        (\(tempName, tempHandle) -> do
            hClose tempHandle
            removeFile tempName)

        (\(tempName, tempHandle) -> do
            hPutStr tempHandle newTodoItems
            hClose tempHandle
            removeFile "todo.txt"
            renameFile tempName "todo.txt")

ビルド・実行

> stack ghc -- --make todo.hs
[1 of 1] Compiling Main             ( todo.hs, todo.o )
Linking todo ...
> ./todo view todo.txt
0 - chinchin
1 - chinchinchin
> ./todo add todo.txt "bump of chicken"
> ./todo view todo.txt
0 - chinchin
1 - chinchinchin
2 - bump of chicken
> ./todo bump todo.txt 2
> ./todo view todo.txt
0 - bump of chicken
1 - chinchin
2 - chinchinchin

で、できた...

不正な入力に対応する

全て拾うパターンをdispatchの最後に追加する

dispatch :: String -> [String] -> IO ()
dispatch "add" = add
dispatch "view" = view
dispatch "remove" = remove
dispatch "bump" = bump
dispatch command = doesntExist command

doesntExist :: String -> [String] -> IO ()
doesntExist command =
    putStrLn $ "The " ++ command ++ " command doesn't exist"

また、add, view, remove それぞれの関数でも全てを拾うパターンを追加する。 引数の数が違う場合にユーザーに知らせるようにする。

add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")
add _ = putStrLn "The add command takes exactly two arguments"
...

実行・ビルド

> stack ghc -- --make todo.hs
[1 of 1] Compiling Main             ( todo.hs, todo.o )
Linking todo ...
> ./todo oppai
The oppai command doesn't exist
> ./todo add todo.txt
The add command takes exactly two arguments
> ./todo view todo.txt hogeee
The view command takes exactly one arguments
> ./todo remove todo.txt
The remove command takes exactly two arguments
> ./todo bump
The bump command takes exactly two arguments

おk

まだ、./todo で実行するとエラーとなる

> ./todo
todo: user error (Pattern match failure in do expression at todo.hs:15:5-21)

またあとで対応する予定

所感

だいぶ実用的なプログラムっぽいし、bump はなんとか自分で書けたので良かった。 (うまく書けているかはわからん)

CLIのプログラムはこのパターンを応用すれば書いていけそう。

dispatch から各コマンドを呼び出すのに関数を返しているが、 このように書いていく感覚を早く掴んで自分でスラスラ書いていけるようになりたい。

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

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

  • 作者: MiranLipovaca
  • 出版社/メーカー: オーム社
  • 発売日: 2017/07/14
  • メディア: Kindle版
  • 購入: 4人 クリック: 9回
  • この商品を含むブログを見る