Haskell随笔及Todo应用实现

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

相传,当年长生击败震山虎的功夫正是江湖失传已久的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代码......」所说的:

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

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也是任性地醉了):

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

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容