2011-08-29 26 views
9

Tôi sẽ quan tâm đến một ví dụ nhỏ của van Laarhoven isomorphism lenses, được áp dụng cho một kiểu dữ liệu như data BValue = BValue { π :: Float, σ :: Float, α :: Float } deriving Show (cụ thể là các chức năng get/set/modify). Cảm ơn bạn trước.Ống kính đẳng cấu

Trả lời

6

Từ bài van Laarhoven của, loại Lens

data Lens a b = forall r . Lens (Iso a (b, r)) 

Vì vậy, trong trường hợp của chúng tôi aBValue và chúng tôi muốn xây dựng một số leneses đó chọn ra một hoặc nhiều các yếu tố. Vì vậy, ví dụ, hãy xây dựng một ống kính chọn ra π.

piLens :: Lens BValue Float 

Vì vậy, nó sẽ là một ống kính từ một BValue đến một Float

piLens = Lens (Iso {fw = piFwd, bw = piBwd}) 

Một ống kính chọn ra hai điều (cụ thể là người đầu tiên trong ba, với nhãn pi.): A còn lại loại r (bỏ qua ở đây vì chúng tôi không phải chỉ định rõ ràng một loại tồn tại trong haskell), và một đẳng cấu. Một đẳng cấu là lần lượt bao gồm một chuyển tiếp và một chức năng lạc hậu.

piFwd :: BValue -> (Float, (Float, Float)) 
piFwd (BValue {pi, sigma, alpha}) = (pi, (sigma, alpha)) 

Chức năng chuyển tiếp chỉ cách ly thành phần chúng tôi muốn. Lưu ý rằng loại dư của tôi ở đây là "phần còn lại của giá trị", cụ thể là một cặp sigma và phao alpha.

piBwd :: (Float, (Float, Float)) -> BValue 
piBwd (pi, (sigma, alpha)) = BValue { pi = pi, sigma = sigma, alpha = alpha } 

Chức năng lạc hậu là tương tự.

Bây giờ chúng tôi đã xác định ống kính để thao tác thành phần pi của BValue.

Bảy ống kính khác tương tự nhau. (7 ống kính: chọn ra sigma và alpha, chọn tất cả các cặp có thể (bỏ qua thứ tự), chọn tất cả BValue và chọn ra ()).

Một chút tôi không chắc chắn về độ nghiêm ngặt: Tôi hơi lo lắng rằng các hàm fw và bw tôi đã viết quá nghiêm ngặt. Không chắc.

Chúng tôi đang chưa xong:

Chúng tôi vẫn cần phải kiểm tra xem piLens thực sự tôn trọng pháp luật ống kính. Điều tốt đẹp về định nghĩa của Van Laarhoven là Lens là chúng ta chỉ phải kiểm tra các định luật đẳng cấu; các luật ống kính tuân theo tính toán trong bài đăng trên blog của anh ấy.

Vì vậy, nghĩa vụ chứng minh của chúng tôi là:

  1. fw piLens . bw piLens = id
  2. bw piLens . fw piLens = id

Cả hai chứng minh làm theo trực tiếp từ các định nghĩa của piFwdpiBwd và pháp luật về thành phần.

+2

Điều cuối cùng: Không có chức năng nhận/đặt/sửa đổi cho 'BValue'. Các hàm get/set/modify được định nghĩa một lần và forall cho mọi kiểu 'Lens a b'. (Xem bài đăng trên blog). Điều duy nhất còn lại cần làm là áp dụng, ví dụ, 'get' vào một ống kính cụ thể phù hợp với loại dữ liệu của bạn. Vì vậy: 'get piLens' là getter cho trường pi của' BValue'. – Lambdageek

+0

Làm thế nào để sử dụng những người truy cập này trên một biến cụ thể của loại đã cho, ví dụ: 'bv :: BValue'? Ống kính áp dụng cho một loại, do đó, có thể 'get' hoặc' set' các trường của một biến thuộc loại đó. –

+0

@mindbound chỉ áp dụng chức năng get/set/modify cho cả ống kính và giá trị: 'get piLens bv' hoặc' modify piLens (+ 1.0) bv' – Lambdageek

2

Khám phá , triển khai thấu kính cho các loại bản ghi.

Để minh họa gói này, hãy lấy hai kiểu dữ liệu ví dụ sau.

import Data.Label 
import Prelude hiding ((.), id) 

data Person = Person 
{ _name :: String 
, _age :: Int 
, _isMale :: Bool 
, _place :: Place 
} 

data Place = Place 
{ _city 
, _country 
, _continent :: String 
} 

Cả hai kiểu dữ liệu đều là loại bản ghi với tất cả các nhãn có dấu gạch dưới. Dấu gạch dưới này là một dấu hiệu cho mã Haskell mẫu của chúng tôi để lấy được các ống kính cho các trường này. Ống kính có thể được thực hiện với lớp lót đơn giản này:

$(mkLabels [''Person, ''Place]) 

Đối với tất cả các nhãn, ống kính sẽ được tạo.

Bây giờ hãy xem ví dụ này. Người bạn cũ 71 tuổi này, hàng xóm của tôi tên là Jan, không ngại sử dụng anh ta như một ví dụ:

jan :: Person 
jan = Person "Jan" 71 True (Place "Utrecht" "The Netherlands" "Europe") 

Khi chúng ta muốn chắc chắn Jan thực sự già như anh ta tuyên bố chúng ta có thể sử dụng hàm get để lấy độ tuổi dưới dạng số nguyên:

hisAge :: Int 
hisAge = get age jan 

Cân nhắc bây giờ anh ấy muốn chuyển đến Amsterdam: nơi nào tốt hơn để dành ngày cũ của bạn. Sử dụng thành phần chúng ta có thể thay đổi giá trị thành phố sâu bên trong cấu trúc:

moveToAmsterdam :: Person -> Person 
moveToAmsterdam = set (city . place) "Amsterdam" 

Và bây giờ:

ghci> moveToAmsterdam jan 
Person "Jan" 71 True (Place "Amsterdam" "The Netherlands" "Europe") 

Thành phần được thực hiện bằng cách sử dụng toán tử là một phần của các mô-đun Control.Category (.). Đảm bảo nhập mô-đun này và ẩn hàm (.) Id mặc định từ Haskell Prelude.

Các vấn đề liên quan