Cơ bản về Turtle - Haskell
Haskell
17
script
2
White

viethnguyen viết ngày 10/10/2015

Mở đầu

Sau một thời gian tìm hiểu những khái niệm cơ bản của Haskell (khá mất thời gian), dạo gần đây tôi bắt đầu chuyển sang tìm hiểu những library nổi tiếng của Haskell. Thư viện dễ dàng để bắt đầu nhất có lẽ là Turtle của Gabriel Gonzalez, dùng để viết shell scripts và command line tools.

Trong bài viết này, tôi sẽ thử dùng Turtle để thực hiện một task như sau:

Tôi có một thư mục phd, trong đó chứa các dữ liệu làm việc của tôi. Lệnh tree cho thấy nội dung bên trong như sau:

➜  phd    tree -L 1
.
|-- docs
|-- Makefile
|-- misc
|-- paper-projects
|-- project-high-speed-camera
|-- project-screen-id

5 directories, 1 file

Các thư mục chứa project tôi thực hiện được bắt đầu bằng project-. Tôi muốn viết một chương trình in ra tên các project mà tôi đang làm, với format đẹp hơn, ví dụ như "High Speed Camera" (tất cả các chữ cái đầu chứ được viết hoa).

Có thể ví dụ này khá dễ để code trong vài ngôn ngữ khác, nhưng tôi thấy đây là một ví dụ tốt để trình bày các khái niệm của thư viện Turtle.

Nào chúng ta cùng bắt đầu.

Lọc ra các thư mục chứa project

Điều đầu tiên cần làm là tôi phải lọc ra các thư mục có tên bắt đầu bằng chữ project-.

  phdDir = "/home/vagrant/phd"

  -- list all project dirs in `phd` folder
  listProjectDirs :: Shell Text
  listProjectDirs = grep (prefix "project") subdirs
      where subdirs = liftM (T.pack . encodeString . filename) $  ls phdDir

Thư viện Turtle định nghĩa một kiểu dữ liệu Shell. Theo tài liệu hướng dẫn (Turtle.Shell), Shell a là một dòng dữ liệu kiểu a được protected, có kèm theo side effect. Trong Turtle, các hàm quen thuộc của Unix được implement lại và có kiểu dữ liệu đầu ra chính là Shell. Ở đây, listProjectDirs có kiểu Shell Text, bạn có thể hiểu nôm na là nó là một stream chứa toàn dữ liệu kiểu Text, và chúng ta sẽ thao tác trên stream này.

Chúng ta cùng nhìn hàm này từ cuối lên. Đầu tiên, ls phdDir sẽ cho tôi một dòng dữ liệu Shell FilePath, chứa tất cả đường dẫn của các thư mục con của thư mục phd. Vì Shell cũng là một monad, nên tôi có thể sử dụng hàm liftM để chuyển đổi các dữ liệu bên trong Shell stream. Tôi chuyển đổi qua vài bước như sau: tách riêng tên thư mục (hàm filename), chuyển kiểu FilePath thành kiểu String (hàm encodeString), rồi chuyển kiểu String thành kiểu Text (hàm T.pack, với T là tên viết tắt của Data.Text). Cuối cùng tôi có subdirs là một dòng dữ liệu Shell Text, chứa tên các thư mục dưới kiểu Text.

Tiếp theo, tôi sẽ lọc dòng dữ liệu này chỉ để lấy ra các thư mục có tên bắt đầu bằng project. Việc này được thực hiện khá dễ dàng do Turtle đã implement hàm grep :: Pattern a -> Shell Text -> Shell Text. Tôi chỉ cần cung cấp tham số cho hàm này. Tham số thứ hai có kiểu Shell Text, có lẽ bạn cũng đoán được là tôi sẽ dùng subdirs ở trên. Tham số thứ nhất có kiểu Pattern, là một kiểu dữ liệu từa tựa như regular expression được implement trong Turtle. Bạn có thể tham khảo thêm ở đây: Turtle.Pattern. Ở đây, tôi chỉ dùng một Pattern đơn giản là prefix "project", tức là chọn ra các tên bắt đầu bằng project.

Xong rồi, bây giờ tôi muốn check xem dòng dữ liệu listProjectDirs như thế nào. Muốn xem trước một data stream kiểu Shell, tôi dùng hàm view:

λ>  view listProjectDirs
"project-high-speed-camera"
"project-screen-id"

Bạn thấy đó, tôi đã lọc ra được 2 thư mục chứa project rồi :D

Lấy ra tên của project

Việc tiếp theo tôi sẽ làm là chuyển đổi tên thư mục của project thành tên project. Ví dụ như project-high-speed-camera sẽ trở thành High Speed Camera.

  extractProjectName :: Text -> Text
  extractProjectName path = Prelude.head x
      where x = match pattern path
            word = do
              x <- anyChar
              let y = T.toUpper $ T.pack [x]
              remain <- many letter
              let z = T.pack remain
              return $ T.concat [y,z, " "]
            pattern = do
              "project-";
              x <- word `sepBy` char '-'
              return $ T.concat x

Trong Turtle.Pattern có định nghĩa hàm match :: Pattern a -> Text -> [a] để match một đối tượng Text và xuất ra dữ liệu kiểu a. Tham số thứ nhất Pattern a là nơi tôi sẽ định nghĩa kiểu xâu tôi muốn xuất ra. Việc định nghĩa Pattern khá giống như định nghĩa parser combinator như trong Parsec (bạn có thể xem một bài viết trước của tôi) ở chỗ định nghĩa một Pattern từ những Pattern nhỏ hơn. Có lẽ tôi nên dành cho bạn đọc tìm hiểu hai Pattern ở trên là wordpattern :)

Lưu ý là hàm match sẽ trả về một list các kết quả có thể, nên ở đây tôi dùng hàm head để chỉ chọn một kết quả.

Xuất kêt quả ra màn hình console

Trong Turtle.Prelude có hàm stdout :: MonadIO io => Shell Text -> io () dùng để xuất ra màn hình console, tôi dùng hàm này để display một Shell Text

  -- list all project names
  listProjectNames :: IO ()
  listProjectNames = stdout ("Current projects: " <|>
                             (do
                               dir <- listProjectDirs
                               let name = extractProjectName dir
                               return name
                             )
                            )

Command line interface

Phần này không liên quan lắm đến đề bài đặt ra, nhưng tôi muốn giời thiệu một tính năng của Turtle để dễ dàng tạo ra command line interface với các options. Module Turtle.Options giúp bạn thực hiện điều này. Việc áp dụng là khá dễ dàng, tác giả module đã cho ví dụ khá tốt, tôi chỉ cân bắt chước và sửa đổi một chút :D

  -- Command line interface to this tool
  parser :: Parser Text
  parser = optText "function" 'f' "Choose functions"

  main = do
    functionName <- options "My command line tool for Ph.D data management" parser
    case functionName of
      "view" -> listProjectNames
      otherwise -> putStrLn "Unrecognized function!"

Chương trình hoàn chỉnh

{-# LANGUAGE DeriveDataTypeable #-}
  {-# LANGUAGE OverloadedStrings  #-}

  module Main where

  import qualified Control.Foldl             as Fold
  import           Control.Monad
  import           Data.Text                 as T
  import           Filesystem.Path.CurrentOS
  import           Prelude                   hiding (FilePath)
  import           System.Console.CmdArgs
  import           Turtle                    as TU

  phdDir = "/home/vagrant/phd"

  -- list all project dirs in `phd` folder
  listProjectDirs :: Shell Text
  listProjectDirs = grep (prefix "project") subdirs
      where subdirs = liftM (T.pack . encodeString . filename) $  ls phdDir

  -- list all project names
  listProjectNames :: IO ()
  listProjectNames = stdout ("Current projects: " <|>
                             (do
                               dir <- listProjectDirs
                               let name = extractProjectName dir
                               return name
                             )
                            )

  extractProjectName :: Text -> Text
  extractProjectName path = Prelude.head x
      where x = match pattern path
            word = do
              x <- anyChar
              let y = T.toUpper $ T.pack [x]
              remain <- many letter
              let z = T.pack remain
              return $ T.concat [y,z, " "]
            pattern = do
              "project-";
              x <- word `sepBy` char '-'
              return $ T.concat x


  -- Command line interface to this tool
  parser :: Parser Text
  parser = optText "function" 'f' "Choose functions"

  main = do
    functionName <- options "My command line tool for Ph.D data management" parser
    case functionName of
      "view" -> listProjectNames
      otherwise -> putStrLn "Unrecognized function!"

Sau cùng, tôi build chương trình và chạy nó:

➜  phdtool git:(master)   stack build --copy-bins
Copying from /home/vagrant/code/phdtool/.stack-work/install/i386-linux/lts-3.1/7.10.2/bin/phdtool to /home/vagrant/.local/bin/phdtool

Copied executables to /home/vagrant/.local/bin/:
- phdtool
➜  phdtool git:(master)   phdtool
Usage: phdtool (-f|--function FUNCTION)
➜  phdtool git:(master)   phdtool --help
My command line tool for Ph.D data management

Usage: phdtool (-f|--function FUNCTION)

Available options:
  -h,--help                Show this help text
  -f,--function FUNCTION   Choose functions
➜  phdtool git:(master)   phdtool -f view
Current projects:
High Speed Camera
Screen Id

Wow, bạn thấy đó, tôi đã có một command line tool nhìn trông cũng khá pro đấy chứ :D

Kết luận

Trên đây chỉ là một ví dụ để minh họa những khái niệm cơ bản của thư viện Turtle. Để tìm hiểu thêm, bạn có thể tham khảo Tutorial rất tốt của Gabriel: Turtle.Tutorial. Turtle được đánh giá là một thư viện để viết shell scripts khá mạnh trên Haskell, rất đáng để tìm hiểu.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

viethnguyen

12 bài viết.
29 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
14 4
Giới thiệu về Arduino Có thể bạn đã quen lập trình trên PC, với những ngôn ngữ như C, C++, C, Java, Python, Ruby... Nhưng bạn có biết là phần mềm...
viethnguyen viết 3 năm trước
14 4
White
8 0
Trong bài viết này, tôi sẽ trình bày về một đặc tính của Haskell khá khác biệt so với các ngôn ngữ lập trình khác, đó là laziness (dịch tiếng việt ...
viethnguyen viết hơn 3 năm trước
8 0
White
6 0
Giới thiệu Quy hoạch động là một trong những kĩ thuật lập trình cơ bản được sử dụng khá nhiều trong các cuộc thi lập trình. Ý tưởng về cơ bản rất ...
viethnguyen viết 3 năm trước
6 0
Bài viết liên quan
White
34 10
Thời kỳ mới đi làm tôi nghĩ cứ phải gõ thật nhiều cho quen cho nhớ nhưng lâu dần việc đó cho cảm giác thật nhàm chán. Hiện giờ, những gì tôi hay là...
manhdung viết 3 năm trước
34 10
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
12 bài viết.
29 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!