คุยกับเพื่อนๆในกลุ่ม Rust Telegram แล้วอยากเห็นว่า Haskell นั่นต่อ DB ยังไงเมื่อเทียบกับ Rust ที่ใช้ proc macro ช่วยให้โค้ดดูง่ายขึ้น
ค้นๆดูแล้วนั้น Haskell ใช้ library ที่ชื่อว่า persistent ซึ่งตัวนี้ทำหน้าที่สร้าง datatype ที่ map กับ table ให้ด้วยผ่านฟีเจอร์ของ GHC (compiler haskell ที่รองรับฟีเจอร์มากกว่า standard กำหนดผ่านทาง language extension) ที่ชื่อว่า TemplateHaskell
ลองเขียนต่อ MySQL เพื่อ query ง่ายๆออกมาได้แบบนี้
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Main where
import Database.Persist
import Database.Persist.TH
import Database.Persist.Sql
import Control.Monad.IO.Class
import Control.Monad.Reader
import Database.Persist.MySQL
import Control.Monad.Logger
share [mkPersist sqlSettings] [persistLowerCase|
Person
name String
age Int Maybe
deriving Show
|]
listPerson :: MonadIO m => SqlPersistT m [Entity Person]
listPerson = selectList [] []
conn :: ConnectInfo
conn = defaultConnectInfo {
connectPassword = "password",
connectHost = "0.0.0.0",
connectDatabase = "learning_sql"
}
main :: IO ()
main = runStderrLoggingT $ withMySQLPool conn 1 $ runSqlPool $ do
me <- (entityVal.head) <$> listPerson
p $ "Name: " <> (personName me)
case personAge me of
Just age -> p $ "Age: " <> (show age)
_ -> return ()
where
p = liftIO . putStrLn
จะเห็นว่าต้อง enable language extension เยอะพอสมควรเลย :D
โค้ดตรงที่ใช้ TemplateHaskell ช่วยสร้าง datatype ให้เองคือตรงนี้
share [mkPersist sqlSettings] [persistLowerCase|
Person
name String
age Int Maybe
deriving Show
|]
จะสร้าง datatype แบบนี้ให้
data Person
= Person {personName :: !String, personAge :: !(Maybe Int)}
ซึ่งมันจะไป map กับ table person
ที่มี columns แบบนี้
MariaDB [learning_sql]> desc person;
+-------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| age | int(11) | YES | | NULL | |
+-------+--------------+------+-----+---------+----------------+
ส่วนที่เขียน query คือฟังก์ชัน listPerson
listPerson :: MonadIO m => SqlPersistT m [Entity Person]
listPerson = selectList [] []
ซึ่ง context ของคำสั่งในการ query ต่างๆจะอยู่ภายใต้ reader monad transformer SqlPersistT m
สำหรับ connection เราใส่รายละเอียดใน type ConnectInfo
โดยเอาค่าจาก defaultConnectInfo
มาปรับนิดหน่อย
conn :: ConnectInfo
conn = defaultConnectInfo {
connectPassword = "password",
connectHost = "0.0.0.0",
connectDatabase = "learning_sql"
}
ส่วนใน main ก็แค่ลองเรียกแล้วเอาค่าแรกออกมาดู เราก็เขียนโค้ด query ต่างๆใน context ของ SqlPersistT m
เสร็จแล้วพอจะประกอบร่างก็ต้อง runwrap monad transformer กันก่อน โดยใช้ runSqlPool จากนั้นเราก็เรียก withMySQLPool
เพื่อสร้าง connection ไปรับ query สุดท้ายเนื่องจาก withMySQLPool
ได้ผลลัพธ์ออกมาเป็น MonadLogger
เราก็เลยต้องใช้ runStderrLoggingT
เพื่อเลือกให้มัน log ค่าต่างๆออก standard error พร้อมกับ unwrap ค่าที่ได้ออกมาเป็น IO ปกติเพื่อกำหนดให้เป็น main ฟังก์ชันได้
main :: IO ()
main = runStderrLoggingT $ withMySQLPool conn 1 $ runSqlPool $ do
me <- (entityVal.head) <$> listPerson
p $ "Name: " <> (personName me)
case personAge me of
Just age -> p $ "Age: " <> (show age)
_ -> return ()
where
p = liftIO . putStrLn
โค้ดก็ประมาณนี้ ใครไม่เคยเขียน Haskell หรือไม่เข้าใจ Monad Transformer มาก่อนอาจจะงงๆ แต่ใช้งานจริงๆก็ไม่ยากมาก ตรง TemplateHaskell ช่วยได้เยอะเลยในการลดโค้ดที่ต้องเขียนเองสำหรับการแปลงไปมาระหว่าง column กับ datatype ใน Haskell
ขอฝาก Buy Me a Coffee
สำหรับท่านใดที่อ่านแล้วชอบโพสต์ต่างๆของผมที่นี่ ต้องการสนับสนุนค่ากาแฟเล็กๆน้อยๆ สามารถสนับสนุนผมได้ผ่านทาง Buy Me a Coffee คลิ๊กที่รูปด้านล่างนี้ได้เลยครับ
ส่วนท่านใดไม่สะดวกใช้บัตรเครดิต หรือ Paypal สามารถสนับสนุนผมได้ผ่านทาง PromptPay โดยดู QR Code ได้จากโพสต์ที่พินเอาไว้ได้ที่ Page DevDose ครับ https://web.facebook.com/devdoseth
Top comments (0)