Thiết kế Lens và Isos tùy chỉnh
-- Một số ví dụ trong chương này yêu cầu các tiện ích mở rộng GHC:
-- TemplateHaskell cần cho makeLenses; RankNTypes cần cho
-- một số chữ ký kiểu sau này.
{-# LANGUAGE TemplateHaskell, RankNTypes #-}
import Control.Lens
import Control.Monad.State
data ViTri = ViTri
{ _toaDoX :: Double
, _toaDoY :: Double
} deriving (Show)
makeLenses ''ViTri
data Doan = Doan
{ _diemDau :: ViTri
, _diemCuoi :: ViTri
} deriving (Show)
makeLenses ''Doan
taoViTri :: (Double, Double) -> ViTri
taoViTri (x, y) = ViTri x y
taoDoan :: (Double, Double) -> (Double, Double) -> Doan
taoDoan diemdau diemcuoi = Doan (taoViTri diemdau) (taoViTri diemcuoi)
kiemTraDiem = taoViTri (2,3)
kiemTraDoan = taoDoan (0, 1) (2, 4)
toaDoDiem :: Traversal ViTri ViTri Double Double
-- :: Applicative f => (Double -> f Double) -> ViTri -> f ViTri
toaDoDiem h (ViTri x y) = ViTri <$> h x <*> h y
loaiBoNeuAm gia_tri = if gia_tri < 0 then Nothing else Just gia_tri
toaDoMoi :: Traversal Doan Doan Double Double
-- :: Applicative f => (Double -> f Double) -> Doan -> f Doan
toaDoMoi h (Doan batdau ketthuc) =
Doan <$> toaDoDiem h batdau <*> toaDoDiem h ketthuc
nhanDoiDoan :: Double -> Doan -> Doan
nhanDoiDoan he_so = over toaDoMoi (he_so *)
minhHoaTrangThai :: State Doan ()
minhHoaTrangThai = do
diemDau .= taoViTri (0,0)
zoom diemCuoi $ do
toaDoX += 1
toaDoY *= 2
toaDoDiem %= negate
chuyenDoiViTri :: ViTri -> (Double, Double)
chuyenDoiViTri (ViTri x y) = (x,y)
capToaDo :: Iso' ViTri (Double, Double)
capToaDo = iso chuyenDoiViTri taoViTri
Các bước tạo Lens tùy chỉnh:
- {-# LANGUAGE TemplateHaskell, RankNTypes #-} Kích hoạt tiện ích mở rộng ngôn ngữ
- import Control.Lens Nhập thư viện Lens
- data ViTri = ViTri { _toaDoX :: Double, _toaDoY :: Double } Định nghĩa cấu trúc dữ liệu với tên trường bắt đầu bằng dấu gạch dưới
- makeLenses ''Doan Sử dụng makeLenses ''TenKieu để tạo các lens
Các bước tạo Isos tùy chỉnh:
- import Control.Lens Nhập thư viện Lens
- taoViTri :: (Double, Double) -> ViTri taoViTri (x, y) = ViTri x y chuyenDoiViTri :: ViTri -> (Double, Double) chuyenDoiViTri (ViTri x y) = (x,y) Chuẩn bị hai hàm có kiểu vào/ra ngược nhau
- capToaDo :: Iso' ViTri (Double, Double) capToaDo = iso chuyenDoiViTri taoViTri Dùng hàm iso để kết hợp hai hàm thành một Iso
*Main> view diemCuoi kiemTraDoan
ViTri {_toaDoX = 2.0, _toaDoY = 4.0}
*Main> set diemCuoi (taoViTri (2, 3)) kiemTraDoan
Doan {_diemDau = ViTri {_toaDoX = 0.0, _toaDoY = 1.0}, _diemCuoi = ViTri {_toaDoX = 2.0, _toaDoY = 3.0}}
*Main> view (diemCuoi . toaDoY) kiemTraDoan
4.0
*Main> over (diemCuoi . toaDoY) (2 *) kiemTraDoan
Doan {_diemDau = ViTri {_toaDoX = 0.0, _toaDoY = 1.0}, _diemCuoi = ViTri {_toaDoX = 2.0, _toaDoY = 8.0}}
*Main> kiemTraDoan ^. diemCuoi
ViTri {_toaDoX = 2.0, _toaDoY = 4.0}
*Main> kiemTraDoan & diemCuoi .~ taoViTri (2, 3)
Doan {_diemDau = ViTri {_toaDoX = 0.0, _toaDoY = 1.0}, _diemCuoi = ViTri {_toaDoX = 2.0, _toaDoY = 3.0}}
*Main> kiemTraDoan ^. diemCuoi . toaDoY
4.0
*Main> kiemTraDoan & diemCuoi . toaDoY %~ (2*)
Doan {_diemDau = ViTri {_toaDoX = 0.0, _toaDoY = 1.0}, _diemCuoi = ViTri {_toaDoX = 2.0, _toaDoY = 8.0}}
Toán tử (.) được dùng để kết hợp hai lens.
*Main> toaDoDiem loaiBoNeuAm (taoViTri (1, 2))
Just (ViTri {_toaDoX = 1.0, _toaDoY = 2.0})
*Main> toaDoDiem loaiBoNeuAm (taoViTri (-1, 2))
Nothing
*Main> over toaDoDiem negate (taoViTri (1, 2))
ViTri {_toaDoX = -1.0, _toaDoY = -2.0}
*Main> set toaDoDiem 7 (taoViTri (1, 2))
ViTri {_toaDoX = 7.0, _toaDoY = 7.0}
*Main> toListOf toaDoMoi (taoDoan (0, 1) (2, 3))
[0.0,1.0,2.0,3.0]
*Main> nhanDoiDoan 2 (taoDoan (0, 1) (2, 3))
Doan {_diemDau = ViTri {_toaDoX = 0.0, _toaDoY = 2.0}, _diemCuoi = ViTri {_toaDoX = 4.0, _toaDoY = 6.0}}
*Main> execState minhHoaTrangThai (taoDoan (1,2) (5,3))
Doan {_diemDau = ViTri {_toaDoX = 0.0, _toaDoY = 0.0}, _diemCuoi = ViTri {_toaDoX = -6.0, _toaDoY = -6.0}}
*Main> import Data.Tuple (swap)
*Main Data.Tuple> view capToaDo kiemTraDiem -- Tương đương với chuyenDoiViTri
(2.0,3.0)
*Main Data.Tuple> view (capToaDo . _2) kiemTraDiem
3.0
*Main Data.Tuple> over capToaDo swap kiemTraDiem
ViTri {_toaDoX = 3.0, _toaDoY = 2.0}
*Main Data.Tuple> view (from capToaDo) (2,3) -- Tương đương với taoViTri
ViTri {_toaDoX = 2.0, _toaDoY = 3.0}
*Main Data.Tuple> view (from capToaDo . toaDoY) (2,3)
3.0
Xây dựng Prisms tùy chỉnh
{-# LANGUAGE TemplateHaskell, RankNTypes #-}
import Control.Lens
data CongViecMoi =
CongViecDonGian String |
CongViecPhucTap String Int |
CongViecTongHop String [CongViecMoi]
deriving (Show)
makePrisms ''CongViecMoi
cv1 = CongViecDonGian "Dọn dẹp"
cv2 = CongViecPhucTap "Dọn bếp" 15
cv3 = CongViecTongHop "Dọn nhà" [cv1,cv2]
Các bước tạo prisms tùy chỉnh:
- {-# LANGUAGE TemplateHaskell, RankNTypes #-} Kích hoạt tiện ích mở rộng ngôn ngữ
- import Control.Lens Nhập thư viện Lens
- data CongViecMoi = CongViecDonGian | CongViecPhucTap | CongViecTongHop Định nghĩa kiểu đại số với các constructor
- makePrisms ''CongViecMoi Sử dụng makePrisms ''TenKieu để tạo các prisms
- Lưu ý rằng khác với makeLenses, ở đây tên constructor không có dấu gạch dưới nhưng tên prism lại có tiền tố gạch dưới
*Main> cv1 ^? _CongViecDonGian
Just "Dọn dẹp"
*Main> cv2 ^? _CongViecPhucTap
Just ("Dọn bếp",15)
*Main> cv2 ^? _CongViecPhucTap._2
Just 15
*Main> cv2 & _CongViecDonGian .~ "Dọn gara"
CongViecPhucTap "Dọn bếp" 15
*Main> cv2 & _CongViecPhucTap._2 .~ 30
CongViecPhucTap "Dọn bếp" 30