Câu hỏi Trộn và kết hợp tính toán trạng thái trong đơn vị trạng thái


Trạng thái chương trình của tôi bao gồm ba giá trị, a, bc, các loại A, BC. Các chức năng khác nhau cần truy cập vào các giá trị khác nhau. Tôi muốn viết các hàm bằng cách sử dụng State monad để mỗi chức năng chỉ có thể truy cập vào các phần của trạng thái mà nó cần truy cập.

Tôi có bốn chức năng của các loại sau:

f :: State (A, B, C) x
g :: y -> State (A, B) x
h :: y -> State (B, C) x
i :: y -> State (A, C) x

Đây là cách tôi gọi g trong f:

f = do
    -- some stuff
    -- y is bound to an expression somewhere in here
    -- more stuff
    x <- g' y
    -- even more stuff

    where g' y = do
              (a, b, c) <- get
              let (x, (a', b')) = runState (g y) (a, b)
              put (a', b', c)
              return x

Cái đó g' chức năng là một phần xấu xí của boilerplate mà không có gì ngoài việc thu hẹp khoảng cách giữa các loại (A, B, C) và (A, B). Về cơ bản nó là một phiên bản g hoạt động trên trạng thái 3 tuple, nhưng không thay đổi mục thứ 3. Tôi đang tìm cách viết f không có cái lò hơi đó. Có thể một cái gì đó như thế này:

f = do
    -- stuff
    x <- convert (0,1,2) (g y)
    -- more stuff

Ở đâu convert (0,1,2) chuyển đổi một kiểu tính toán State (a, b) x đánh máy State (a, b, c) x. Tương tự như vậy, đối với tất cả các loại a, b, c, d:

  • convert (2,0,1) chuyển đổi State (c,a) x đến State (a,b,c) x
  • convert (0,1) chuyển đổi State b x đến State (a,b) x
  • convert (0,2,1,0) chuyển đổi State (c,b) x đến State (a,b,c,d) x

Những câu hỏi của tôi:

  1. Có một giải pháp tốt hơn so với đặt giá trị nhà nước trong tuples? Tôi đã nghĩ đến việc sử dụng một chồng biến áp đơn nguyên. Tuy nhiên, tôi nghĩ rằng chỉ hoạt động nếu, đối với bất kỳ hai chức năng nào f và g, hoặc F ⊆ G hoặc là G ⊆ F, Ở đâu F là tập hợp các giá trị trạng thái cần thiết bởi f và G là tập hợp các giá trị trạng thái cần thiết bởi g. Tôi có sai về điều đó không? (Lưu ý rằng ví dụ của tôi không thỏa mãn thuộc tính này. Ví dụ: G = {a, b} và H = {b, c}. Không phải là một tập con của người khác.)
  2. Nếu không có cách nào tốt hơn so với bộ dữ liệu, thì có cách nào tốt để tránh các bản mẫu mà tôi đã đề cập không? Tôi thậm chí còn sẵn sàng để viết một tập tin với một loạt các chức năng boilerplate (xem dưới đây) miễn là các tập tin có thể được tự động tạo ra một lần và sau đó quên về. Có cách nào tốt hơn? (Tôi đã đọc về ống kính, nhưng độ phức tạp của chúng, cú pháp xấu xí, tập hợp khổng lồ các tính năng không cần thiết, và dường như phụ thuộc vào Mẫu Haskell là không đúng. Đó có phải là một quan niệm sai lầm về tôi không? ?)

(Các chức năng tôi đã đề cập sẽ trông giống như thế này.)

convert_0_1_2 :: State (a, b) x -> State (a, b, c) x
convert_0_1_2 f = do
    (a, b, c) <- get
    let (x, (a', b')) = runState f (a, b)
    put (a', b', c)
    return x

convert_0_2_1_0 :: State (c, b) x -> State (a, b, c, d) x
convert_0_2_1_0 f = do
    (a, b, c, d) <- get
    let (x, (b', c')) = runState f (b, c)
    put (a, b', c', d)
    return x

14
2017-11-26 10:11


gốc


Tôi đã không sử dụng nó bản thân mình, nhưng nó có vẻ rất giống lens'S zoom: hackage.haskell.org/package/lens-4.13/docs/… - Joachim Breitner
Điều đó phải dễ dàng với các loại phụ thuộc, nhưng không phải là cực đoan. - user3237465
lens + vinyl làm cho một trải nghiệm phong phú nửa chừng. - András Kovács


Các câu trả lời:


Bạn có thể làm điều đó bằng cách sử dụng tính năng thu phóng từ lens-family hoặc là lens gói với tuple-lenses gói: loại đơn giản hóa của zoom Là:

zoom :: Lens' s a -> State a x -> State s x

Vì thế zoom chạy một phép tính sử dụng một trạng thái nhỏ hơn. Các Lens được sử dụng để xác định vị trí của trạng thái nhỏ hơn a bên trong trạng thái lớn hơn s.

Với hai gói này, bạn có thể chạy g, h và i như sau:

f :: State (A,B,C) x
f = do
  zoom _12 g -- _12 :: Lens' (A,B,C) (A,B)
  zoom _23 h -- _23 :: Lens' (A,B,C) (B,C)
  zoom _13 i -- _13 :: Lens' (A,B,C) (A,C)

9
2017-11-26 17:10



Cảm ơn, đó là những gì tôi đã nhắm đến :-) - Joachim Breitner


Nếu bạn không muốn fuss xung quanh với tuples, bạn có thể sử dụng một "classy" cách tiếp cận với một hồ sơ. Có một số mẫu Haskell ưa thích để hỗ trợ điều này trong lens gói, nhưng bạn cũng có thể làm điều đó bằng tay. Ý tưởng là tạo ít nhất một lớp cho mỗi phần của tiểu bang:

class HasPoints s where
  points :: Lens' s Int

class ReadsPoints s where
  getPoints :: Getter s Int
  default getPoints :: HasPoints s => Getter s Int
  getPoints = points

class SetsPoints s where
  setPoints :: Setter' s Int
  ...

Sau đó, mỗi chức năng điều khiển trạng thái sẽ có một chữ ký kiểu như

fight :: (HasPoints s, ReadsHealth s) => StateT s Game Player

Một hành động với chữ ký cụ thể này có quyền truy cập đầy đủ vào các điểm và quyền truy cập chỉ đọc vào sức khỏe.


4
2017-11-26 19:59



Trường hợp sử dụng phổ biến của tôi: f :: State (A, A) () và g :: State A (). Trong f, Tôi muốn có thể gọi g trên phần đầu tiên hoặc phần thứ hai của fcủa nhà nước. g không nên biết cái nào của hai mảnh đó được truyền vào nó. g thậm chí không nên biết nó là một phần của một ứng dụng lớn hơn. Nhưng với giải pháp sang trọng, f và g sẽ có cùng một loại trạng thái (a Game giá trị) và sử dụng cùng một getters / setters để truy cập bất kỳ phần nào của trạng thái đó, đúng không? Trong khi điều đó cho phép tôi ẩn thông tin từ một hàm với vài trách nhiệm, có vẻ như làm cho chức năng của tôi không thể sử dụng được. - Jordan