Luật cho loại [[a]] -> ([a], [a])


8

Tôi đang cố gắng làm câu hỏi này từ bài tập về nhà của tôi:

Cho tùy ý foo :: [[a]] -> ([a], [a]), viết ra một luật mà hàm foothỏa mãn, liên quan đến mapdanh sách và cặp.

Một số bối cảnh: Tôi là sinh viên năm thứ nhất đang theo học một khóa lập trình chức năng. Trong khi khóa học khá giới thiệu, giảng viên đã đề cập đến nhiều điều ngoài giáo trình, trong số đó là các định lý miễn phí. Vì vậy, sau khi cố gắng đọc báo Wadler, tôi cho rằng, concat :: [[a]] -> [a]với luật pháp map f . concat = concat . map (map f)trông có liên quan đến vấn đề của tôi, kể từ khi chúng ta phải có foo xss = (concat xss, concat' xss)nơi concatconcat'bất kỳ chức năng của loại [[a]] -> [a]. Thế là foothỏa mãn bimap (map f, map g) . foo = \xss -> ((fst . foo . map (map f)) xss, (snd . foo . map (map g)) xss).

Đã 'luật' này dường như quá dài để chính xác, và tôi cũng không chắc về logic của mình. Vì vậy, tôi đã nghĩ đến việc sử dụng một trình tạo định lý miễn phí trực tuyến , nhưng tôi không hiểu ý lift{(,)}nghĩa của nó là gì :

forall t1,t2 in TYPES, g :: t1 -> t2.
 forall x :: [[t1]].
  (f x, f (map (map g) x)) in lift{(,)}(map g,map g)

lift{(,)}(map g,map g)
  = {((x1, x2), (y1, y2)) | (map g x1 = y1) && (map g x2 = y2)}

Làm thế nào tôi nên hiểu đầu ra này? Và làm thế nào tôi nên rút ra luật cho chức năng foođúng?


5
Tôi tin rằng điều này nói lên rằng(\(a,b) -> (map f a, map f b)) . foo = foo . map (map f)
AJFarmar

Câu trả lời:


4

Nếu R1R2là các mối quan hệ (giả sử, R_igiữa A_iB_i, với i in {1,2}), thì lift{(,)}(R1,R2)các cặp quan hệ "được nâng lên", giữa A1 * A2B1 * B2, với *biểu thị sản phẩm (được viết bằng (,)Haskell).

Trong mối quan hệ nâng cao, hai cặp (x1,x2) :: A1*A2(y1,y2) :: B1*B2có liên quan khi và chỉ khi x1 R1 y1x2 R2 y2. Trong trường hợp của bạn, R1R2là các chức năng map g, map g, do đó, nâng cũng trở thành một chức năng : y1 = map g x1 && y2 = map g x2.

Do đó, tạo ra

(f x, f (map (map g) x)) in lift{(,)}(map g,map g)

có nghĩa:

fst (f (map (map g) x)) = map g (fst (f x))
AND
snd (f (map (map g) x)) = map g (snd (f x))

hoặc, nói cách khác:

f (map (map g) x) = (map g (fst (f x)), map g (snd (f x)))

mà tôi đã viết như là, sử dụng Control.Arrow:

f (map (map g) x) = (map g *** map g) (f x)

hoặc thậm chí, theo phong cách pointfree:

f . map (map g) = (map g *** map g) . f

Điều này không có gì đáng ngạc nhiên, vì bạn fcó thể được viết là

f :: F a -> G a
where F a = [[a]]
      G a = ([a], [a])

F, Glà functor (trong Haskell, chúng ta sẽ cần sử dụng một newtypeđể xác định một thể hiện functor, nhưng tôi sẽ bỏ qua điều đó, vì nó không liên quan). Trong trường hợp phổ biến như vậy, định lý miễn phí có một hình thức rất hay: cho mọi g,

f . fmap_of_F g = fmap_of_G g . f

Đây là một hình thức rất đẹp, được gọi là tự nhiên ( fcó thể được hiểu là một sự chuyển đổi tự nhiên trong một thể loại phù hợp). Lưu ý rằng hai fs ở trên thực sự được khởi tạo trên các loại riêng biệt, vì vậy để làm cho các loại đồng ý với phần còn lại.

Trong trường hợp cụ thể của bạn, vì F a = [[a]], nó là thành phần của []functor với chính nó, do đó chúng tôi (không ngạc nhiên) có được fmap_of_F g = fmap_of_[] (fmap_of_[] g) = map (map g).

Thay vào đó, G a = ([a],[a])là thành phần của functor []H a = (a,a)(về mặt kỹ thuật, functor đường chéo được sáng tác với functor sản phẩm). Chúng tôi có fmap_of_H h = (h *** h) = (\x -> (h x, h x)), từ đó fmap_of_G g = fmap_of_H (fmap_of_[] g) = (map g *** map g).


Giải thích tốt đẹp! Chỉ là một câu hỏi: khi bạn nói "với mọi g", g phải hoàn toàn hay nghiêm ngặt, hay không có hạn chế nào cả?
Jingjie YANG

1
@JingjieYANG Vâng, có một số hạn chế nếu chúng tôi sử dụng Haskell. Hầu hết các kết quả như thế này thực sự được thực hiện trong một hệ thống loại thuần túy, nơi mọi kết thúc (do đó nó là tổng số). Trong Haskell, nếu tôi nhớ lại một cách chính xác, vì chúng tôi không chấm dứt, chúng tôi cần phải có gtổng số. Tương tự như vậy, vì chúng ta có seqyêu cầu gnghiêm ngặt. Tôi không chắc chắn 100% về các hạn chế chính xác, nhưng tôi nghĩ chúng nên như vậy. Tuy nhiên, tôi không nhớ mình đã đọc về những điều đó ở đâu - có lẽ trên trang trình tạo định lý miễn phí có một số thông tin.
chi

Không phải Control.Arrow (***) trên tuples một chút lỗi thời, có lợi cho Data.Bifunctor (bimap)? Bất kỳ phản đối để chỉnh sửa để thay đổi sau này?
Joseph Sible-Phục hồi Monica

2
@ JosephSible-RebstateMonica Tôi không có ý tưởng. Tôi đoán đó là một chút như mapvs fmap. Mọi người tiếp tục sử dụng mapvì điều đó làm cho rõ ràng rằng chúng tôi đang xử lý các danh sách (chứ không phải functor khác). Tương tự, (***)chỉ hoạt động trên các cặp (và không phải các bifunctor khác). Có lẽ tôi đang sử dụng nó chủ yếu cho tính năng của nó, vì trong toán học, chúng tôi có xu hướng viết f \times gđể áp dụng bifunctor cho sản phẩm. Có lẽ bimapnên có biến thể infix của nó là tốt, giống như <$>là một biến thể cho fmap.
chi

1
Mặc dù đúng (***)là nó cụ thể hơn bimapở chỗ nó chỉ hoạt động theo cặp chứ không phải là bifunctor tùy ý, nhưng cũng đúng bimaphơn (***)là nó chỉ hoạt động trên các chức năng chứ không phải trên mũi tên tùy ý. Reix, điều đó sẽ không hoàn toàn giống nhau bimapfmapbimapcó 3 tham số và fmapchỉ mất 2.
Joseph Sible-Rebstate Monica

2

Điều tương tự như câu trả lời của @ chi với ít lễ:

Sẽ không có vấn đề gì nếu bạn thay đổi as thành bs trước hoặc sau hàm, bạn sẽ nhận được điều tương tự (miễn là bạn sử dụng một thứ fmapgiống như để làm điều đó).

Với mọi f: a -> b,

    [[a]] -------------> [[b]]
      | (map.map) f |
      | |
     foo foo
      | |
      vv
    ([a], [a]) ---------> ([b], [b])
              bimap ff

đi lại
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.