Haskell随笔及Todo应用实现

Haskell随笔及Todo应用实现_第1张图片

If equal affection cannot be, then let the more loving one be me.
── 我喜爱的一句话,致Haskell。

Haskell随笔及Todo应用实现_第2张图片

相传,当年长生击败震山虎的功夫正是江湖失传已久的Haskell神功!击败震山虎后,长生又凭着「列表解析式」和「Monad式」击溃黑水十面煞的「宏印天远」......长生何许人?如何修得Haskell神功的呢?谜一样传奇相信还有继续......(关于长生:《江湖神功》) (*゚Д゚)つミ匚___

越是了解Haskell,越是喜欢Haskell ── 能够随心,也是一种快乐。如何充分利用函数式思考方式?没有了“永恒”的“for(var key in arr)”又该如何实现原有的循环任务呢?回想当时接触Haskell之前的种种疑惑,如今的感慨确实多。

我对Haskell的认知,并非源于什么函数式是高大上的,或者它的性能是有问题的、它的可测性是混乱等等此类臆断论调。Haskell只是从另一种思维角度,去解决现实中同样的问题。我还想提及Scala,基于我对Scala和Haskell的了解,可以发现Scala的函数式借鉴了大量Haskell思想,有一种亲近感,但Scala同时兼顾了OOP和FP两种编程范式,为解决问题提供了更多手段,因此Scala是我看好的一门语言,虽然复杂度大了点儿。

无意说服你使用Haskell,用Haskell只是我自己的事情,也许你喜欢Lisp,那也同样是你自己的个人选择,好似Emacs和Vim,但我想情怀都是一样的。嗯!每个人都应该有一门饭碗之外的追求和爱好!

Haskell的探知之路是孤寂的,但也充满了乐趣......

之前就构想用Ember写一个Todo应用,那么,用Haskell也实践一个如何呢?嗯,卷起袖管开干!兼顾使用和练手Haskell。

下面列上Todo代码,其中还有一些冗余,可以再精简一些的,但正如我昨晚上在微博「上周末写了点Haskell代码......」所说的:

Haskell随笔及Todo应用实现_第3张图片

对一门技术、一个应用,越到后面越了解、越写越无脑之后,写代码的热情也许会减退,是因为征服感已得到满足了吗?这可能是兴趣爱好跟工程化开发之间的一种差异吧......所以,以后看心情再考虑是否重构吧,关键是要能愉快地耍 (ˇ^ˇ)

import Control.Monad
import Control.Exception
import qualified System.IO.Strict as SIO
import System.IO
import System.IO.Error
import System.Directory
import System.Locale
import System.Environment
import Data.List
import Data.List.Split
import Data.Time
import Text.Printf

main = do
  -- 根据命令行输入参数,确定文件名
  args <- getArgs
  let file = judgeArgExist args
  t <- getCurrentTime
  let fileName =  if file /= ""
                  then "./lists/" ++ file ++ ".TXT"
                  else "./lists/" ++ (formatTime defaultTimeLocale "%Y-%m-%d" t) ++ ".TXT"

  -- 如果文件不存在,则先创建之
  fileExists <- doesFileExist fileName
  if fileExists then return () else appendFile fileName ""

  -- 循环处理事务
  doIt fileName

-- 循环事务处理
-- 参数:文件名
doIt :: String -> IO ()
doIt "" = return ()
doIt fileName = do
  -- 显示菜单项
  displayMenu fileName

  -- 判断输入
  input <- getLine
  exitFlag <- judgeOperation input fileName
  when (exitFlag /= Just "Exit") $ doIt fileName

-- 显示菜单
-- 参数:文件名
displayMenu :: String -> IO()
displayMenu fileName = do
  -- hSetEncoding stdout utf8
  putStrLn $ "\n" ++ printf "   | %-4s | %-10s | %s" "完成" "办理日期" "待办事项内容"
  putStrLn $ foldl1 (++) (take 88 (repeat "-"))
  -- 获取已按Tab分割为[[String]]类型的文件列表
  list <- readTodoFile fileName
  let listTemp = zipWith (\f s -> show f : s) [1..] list
  printList listTemp
  putStrLn $ foldl1 (++) (take 88 (repeat "-"))
  putStrLn "1)添加  2)修改  3)删除  4)已完成  0)退出\n\n请输入操作:"
  where
    printList :: [[String]] -> IO ()
    printList [] = return ()
    printList (l:list) = do
      putStrLn $ printf "%-2s | %-6s | %-14s | %s" (l!!0::String) (l!!1::String) (l!!2::String)  (l!!3::String)
      printList list

-- 判断运行程序时是否指定了文件名参数,如:2015-01-30
-- 参数:文件名
judgeArgExist :: [String] -> String
judgeArgExist [""] = ""
judgeArgExist fileName = if (length fileName) ==1 then head fileName else ""

-- 读取文件
-- 参数:文件名
readTodoFile :: String -> IO [[String]]
readTodoFile file = do
  -- 注意这里用了Strict的readFile,避免后面writeFile时报文件占用的错误,直接在同样一个文件上增删改是比较粗暴...
  contents <- SIO.readFile file
  -- 将文件内容按Tab分割,并保存到一个[[String]]类型的列表listContents中
  return [splitOn "\t" x | x <- (lines contents)]

-- 添加待办事项,追加写入文件
-- 参数:文件名
addTodoFile :: String -> IO ()
addTodoFile fileName = do
  t <- getCurrentTime
  putStrLn "请输入待添加的待办事项:"
  todoItem <- getLine
  appendFile fileName (" \t"
                       ++ (formatTime defaultTimeLocale "%Y-%m-%d" t)
                       ++ "\t"
                       ++ todoItem
                       ++ "\n")

-- 修改待办事项
-- 参数:文件名
updateTodoFile :: String -> IO ()
updateTodoFile fileName = do
  putStrLn "请输入待修改的待办事项序号(不做修改请直接回车):"
  index <- getLine
  if trim index /= ""
  then do
    list <- readTodoFile fileName
    listIndex <- try (evaluate (list!!(read index - 1)!!2)) :: IO (Either SomeException [Char])
    case listIndex of
      Left ex  -> putStrLn $ "转换索引号错误,是否数字或越界?" ++ show ex
      Right val -> do
        putStrLn $ "待修改项是:" ++ list!!(read index - 1)!!2 ++ "。请直接输入修改内容(不做修改请直接回车):"
        todoItem <- getLine
        if todoItem /= ""
        then do
          let listTemp = zipWith (\f s ->
                                   if f == read index then s!!0 ++ "\t" ++ s!!1 ++ "\t" ++ todoItem
                                   else s!!0 ++ "\t" ++ s!!1 ++ "\t" ++ s!!2) [1..] list
          writeFile fileName $ unlines listTemp
        else return ()
  else return ()

-- 删除空白
-- 参数:trim的字符串
trim :: String -> String
trim "" = ""
trim s = let lstrim = dropWhile (`elem` " \t")
         in reverse . lstrim . reverse . lstrim $ s

-- 删除选定列表项
-- 参数:文件名
deleteTodoList :: String -> IO ()
deleteTodoList fileName = do
  putStrLn "请输入待删除的待办事项序号(不做删除请直接回车):"
  index <- getLine
  if trim index /= ""
  then do
    list <- readTodoFile fileName
    listIndex <- try (evaluate (list!!(read index - 1)!!2)) :: IO (Either SomeException [Char])
    case listIndex of
      Left ex  -> putStrLn $ "转换索引号错误,是否数字或越界?" ++ show ex
      Right val -> do
        let listTemp = filter (\s -> s!!0 /= 'D') $
                       zipWith (\f s ->
                                 if f == read index then "D" ++ "\t" ++ s!!1 ++ "\t" ++ s!!2
                                 else s!!0 ++ "\t" ++ s!!1 ++ "\t" ++ s!!2) [1..] list
        writeFile fileName $ unlines listTemp
  else return ()

-- 标识选定列表项为已完成
-- 参数:文件名
completedStamp :: String -> IO ()
completedStamp fileName = do
  putStrLn "请输入已完成的待办事项序号(不做操作请直接回车):"
  index <- getLine
  if trim index /= ""
  then do
    list <- readTodoFile fileName
    listIndex <- try (evaluate (list!!(read index - 1)!!2)) :: IO (Either SomeException [Char])
    case listIndex of
      Left ex  -> putStrLn $ "转换索引号错误,是否数字或越界?" ++ show ex
      Right val -> do
        let listTemp = filter (\s -> s!!0 /= 'D') $
                       zipWith (\f s ->
                                 if f == read index then "*" ++ "\t" ++ s!!1 ++ "\t" ++ s!!2
                                 else s!!0 ++ "\t" ++ s!!1 ++ "\t" ++ s!!2) [1..] list
        writeFile fileName $ unlines listTemp
  else return ()

-- 判断所选操作是哪个:1)添加 2)修改 3)查看 4)删除 5)已完成
-- 参数:所选操作类型 -> 文件名
judgeOperation :: String -> String -> IO (Maybe String)
judgeOperation input fileName
  | input == "1" = do
                     addTodoFile fileName
                     return $ Just "Input."
  | input == "2" = do
                     catch (updateTodoFile fileName) handler
                     return $ Just "Update."
  | input == "3" = do
                     catch (deleteTodoList fileName) handler
                     return $ Just "Delete."
  | input == "4" = do
                     catch (completedStamp fileName) handler
                     return $ Just "CompletedStamp."
  | input == "0" = return $ Just "Exit"
  | otherwise = return Nothing

-- 简单处理一下IO错误
handler :: IOError -> IO ()
handler e
  | isDoesNotExistError e = putStrLn "文件不存在..."
  | otherwise = ioError e

运行结果(别cry,是Windows......还是Linux终端爽啊,但在Linux下运行的结果也一样,Haskell也是任性地醉了):

Haskell随笔及Todo应用实现_第4张图片

顺便show一下我的Emacs...... (~ o ~)~zZ

Haskell随笔及Todo应用实现_第5张图片

在考虑是否把东西放到GitHub中呢?以后把GitHub好好用起来?代码集散地......

4个爱好:编码、读书、打太极,还有0.5个摄影、0.5个画画,都是让人心里平和的好方式。心里烦躁的时候,这些个都是你忠诚的好朋友......

想起来「指环王」中的一句话:“我亲爱的伙伴,我从没怀疑过你。”

Haskell随笔及Todo应用实现_第6张图片

你可能感兴趣的:(Haskell随笔及Todo应用实现)