Tạo và Sử Dụng Lens và Isos trong Haskell

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:

  1. {-# LANGUAGE TemplateHaskell, RankNTypes #-} Kích hoạt tiện ích mở rộng ngôn ngữ
  2. import Control.Lens Nhập thư viện Lens
  3. 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
  4. makeLenses ''Doan Sử dụng makeLenses ''TenKieu để tạo các lens

Các bước tạo Isos tùy chỉnh:

  1. import Control.Lens Nhập thư viện Lens
  2. 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
  3. 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:

  1. {-# LANGUAGE TemplateHaskell, RankNTypes #-} Kích hoạt tiện ích mở rộng ngôn ngữ
  2. import Control.Lens Nhập thư viện Lens
  3. data CongViecMoi = CongViecDonGian | CongViecPhucTap | CongViecTongHop Định nghĩa kiểu đại số với các constructor
  4. makePrisms ''CongViecMoi Sử dụng makePrisms ''TenKieu để tạo các prisms
  5. 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

Thẻ: Haskell lens functional-programming prisms isomorphisms

Đăng vào ngày 19 tháng 6 lúc 16:52