mục đích của mũi tên là gì?


62

Tôi đang học lập trình chức năng với Haskell và tôi cố gắng nắm bắt các khái niệm bằng cách hiểu đầu tiên tại sao tôi cần chúng.

Tôi muốn biết mục tiêu của các mũi tên trong các ngôn ngữ lập trình chức năng. Họ giải quyết vấn đề gì? Tôi đã kiểm tra http://en.wikibooks.org/wiki/Haskell/Under Hiểu_arrowshttp://www.cse.chalmers.se/~rjmh/afp-arrows.pdf . Tất cả những gì tôi hiểu là chúng được sử dụng để mô tả các biểu đồ cho các tính toán và chúng cho phép mã hóa kiểu tự do điểm dễ dàng hơn.

Bài báo cho rằng phong cách miễn phí thường dễ hiểu và dễ viết hơn. Điều này có vẻ khá chủ quan với tôi. Trong một bài viết khác ( http://en.wikibooks.org/wiki/Haskell/StephensArrowTutorial#Hangman:_Main_program ), một trò chơi treo cổ được triển khai, nhưng tôi không thể thấy cách mũi tên làm cho việc triển khai này trở nên tự nhiên.

Tôi có thể tìm thấy rất nhiều bài viết mô tả khái niệm này, nhưng không có gì về động lực.

Tôi đang thiếu gì?

Câu trả lời:


42

Tôi nhận ra rằng tôi đến bữa tiệc muộn, nhưng bạn đã có hai câu trả lời lý thuyết ở đây và tôi muốn cung cấp một giải pháp thay thế thực tế để nhai lại. Tôi đến đây với tư cách là một người thân của Haskell, người dù sao gần đây đã bị bắt buộc thông qua chủ đề Mũi tên cho một dự án mà tôi hiện đang làm.

Đầu tiên, bạn có thể giải quyết một cách hiệu quả hầu hết các vấn đề trong Haskell mà không cần tới Mũi tên. Một số Haskeller đáng chú ý thực sự không thích và không sử dụng chúng (xem ở đây , ở đâyở đây để biết thêm về điều này). Vì vậy, nếu bạn đang nói với chính mình "Này, tôi không cần những thứ này", hãy hiểu rằng bạn thực sự có thể đúng.

Điều tôi cảm thấy bực bội nhất về Mũi tên khi lần đầu tiên học chúng là cách các hướng dẫn về chủ đề này chắc chắn đạt được cho sự tương tự của mạch điện. Nếu bạn nhìn vào mã Mũi tên - ít nhất là loại có đường - thì nó không giống với Ngôn ngữ Định nghĩa Phần cứng. Đầu vào của bạn xếp hàng ở bên phải, đầu ra của bạn ở bên trái và nếu bạn không kết nối chúng đúng cách, chúng chỉ đơn giản là không bắn. Tôi tự nghĩ: Thật sao? Đây có phải là nơi chúng ta đã kết thúc? Chúng ta đã tạo ra một ngôn ngữ hoàn toàn cao cấp đến mức một lần nữa nó bao gồm dây đồng và vật hàn chưa?

Câu trả lời chính xác cho điều này, theo như tôi đã có thể xác định, là: Trên thực tế, có. Trường hợp sử dụng sát thủ ngay bây giờ cho Mũi tên là FRP (nghĩ rằng Yampa, trò chơi, âm nhạc và hệ thống phản ứng nói chung). Vấn đề mà FRP gặp phải phần lớn là cùng một vấn đề mà tất cả các hệ thống nhắn tin đồng bộ khác phải đối mặt: làm thế nào để nối một luồng đầu vào liên tục thành một luồng đầu ra liên tục mà không làm rơi thông tin liên quan hoặc rò rỉ lò xo. Bạn có thể mô hình hóa các luồng dưới dạng danh sách - một số hệ thống FRP gần đây sử dụng phương pháp này - nhưng khi bạn có nhiều danh sách đầu vào gần như không thể quản lý. Bạn cần cách nhiệt với hiện tại.

Những gì Mũi tên cho phép trong các hệ thống FRP là thành phần của các chức năng trong một mạng trong khi đồng thời hoàn toàn trừu tượng hóa mọi tham chiếu đến các giá trị cơ bản được truyền bởi các chức năng đó. Nếu bạn chưa quen với FP, điều này có thể gây nhầm lẫn lúc đầu, và sau đó gây kinh ngạc khi bạn tiếp thu ý nghĩa của nó. Gần đây, bạn chỉ tiếp thu ý tưởng rằng các hàm có thể được trừu tượng hóa và làm thế nào để hiểu một danh sách giống [(*), (+), (-)]như kiểu [(a -> a -> a)]. Với Mũi tên, bạn có thể đẩy sự trừu tượng thêm một lớp.

Khả năng bổ sung này để trừu tượng mang theo những nguy hiểm của riêng nó. Đối với một điều, nó có thể đẩy GHC vào các trường hợp góc mà nó không biết phải làm gì với các giả định loại của bạn. Bạn sẽ phải chuẩn bị để suy nghĩ ở cấp độ loại - đây là một cơ hội tuyệt vời để tìm hiểu về các loại và Xếp hạng và các chủ đề khác như vậy.

Ngoài ra còn có một số ví dụ về cái mà tôi gọi là "Stunt Mũi tên ngu ngốc" nơi người viết mã tìm đến một số tổ hợp Mũi tên chỉ vì anh ta hoặc cô ta muốn thể hiện một mánh khóe gọn gàng bằng bộ dữ liệu. (Đây là đóng góp tầm thường của riêng tôi cho sự điên rồ .) Hãy thoải mái bỏ qua việc ăn xin nóng bỏng như vậy khi bạn bắt gặp nó trong tự nhiên.

LƯU Ý: Như tôi đã đề cập ở trên, tôi là một người mới. Nếu tôi đã ban hành bất kỳ quan niệm sai lầm nào ở trên, xin vui lòng sửa chữa cho tôi.


2
Tôi vui vì tôi chưa chấp nhận bất cứ điều gì. Cảm ơn bạn đã cung cấp câu trả lời này. Nó tập trung hơn vào người dùng. Các ví dụ là tuyệt vời. Các phần chủ quan được xác định rõ ràng và cân bằng. Tôi hy vọng rằng những người đã nêu lên câu hỏi này sẽ quay lại và thấy điều này.
Simon Bergot

Mặc dù mũi tên chắc chắn là công cụ sai cho giải pháp được liên kết của bạn, tôi cảm thấy như tôi cần đề cập đến việc removeAt' n = arr(\ xs -> (xs,xs)) >>> arr (take (n-1)) *** arr (drop n) >>> arr (uncurry (++)) >>> returnAcó thể được viết chính xác và rõ ràng hơn như removeAt' n = (arr (take $ n-1) &&& arr (drop n)) >>> (arr $ uncurry (++)).
cemper93

29

Đây là một câu trả lời "mềm" và tôi không chắc liệu có bất kỳ tài liệu tham khảo nào thực sự nói nó theo cách này không, nhưng đây là cách tôi nghĩ về mũi tên:

Một loại mũi tên A b cvề cơ bản là một hàm b -> cnhưng có cấu trúc nhiều hơn theo cùng một cách mà một giá trị đơn trị M acó cấu trúc nhiều hơn so với cũ đơn giản a.

Bây giờ cấu trúc bổ sung đó xảy ra phụ thuộc vào trường hợp mũi tên cụ thể mà bạn đang nói đến. Cũng như với các đơn nguyên IO aMaybe amỗi đơn vị có cấu trúc bổ sung khác nhau.

Điều bạn nhận được với các đơn nguyên là không có khả năng đi từ một M ađến một a. Bây giờ điều này có vẻ như là một hạn chế, nhưng thực sự đó là một tính năng: hệ thống loại đang bảo vệ bạn khỏi biến một giá trị đơn trị thành một giá trị cũ đơn giản. Bạn chỉ có thể sử dụng giá trị bằng cách tham gia vào đơn nguyên thông qua >>=hoặc các hoạt động nguyên thủy của thể hiện đơn nguyên cụ thể.

Tương tự như vậy, điều bạn nhận được A b clà không có khả năng xây dựng một "chức năng" sản xuất c tiêu thụ b mới. Mũi tên đang bảo vệ bạn khỏi việc tiêu thụ bvà tạo ra cngoại trừ bằng cách tham gia vào các tổ hợp mũi tên khác nhau hoặc bằng cách sử dụng các hoạt động nguyên thủy của thể hiện mũi tên cụ thể.

Ví dụ, các hàm tín hiệu trong Yampa đại khái (Time -> a) -> (Time -> b), nhưng ngoài ra chúng phải tuân theo một hạn chế nguyên nhân nhất định : đầu ra tại thời điểm tđược xác định bởi các giá trị trong quá khứ của tín hiệu đầu vào: bạn không thể nhìn vào tương lai. Vì vậy, những gì họ làm là thay vì lập trình với (Time -> a) -> (Time -> b), bạn lập trình SF a bvà bạn xây dựng các chức năng tín hiệu của mình từ nguyên thủy. Điều đó xảy ra vì nó SF a bhoạt động rất giống một chức năng, do đó cấu trúc phổ biến là thứ gọi là "mũi tên".


"Mũi tên đang bảo vệ bạn khỏi việc tiêu thụ bvà tạo ra cngoại trừ bằng cách tham gia vào các tổ hợp mũi tên khác nhau hoặc bằng cách sử dụng các hoạt động nguyên thủy của trường hợp mũi tên cụ thể." Với lời xin lỗi vì đã trả lời câu trả lời cổ xưa này: câu này khiến tôi nghĩ về các loại tuyến tính, tức là tài nguyên không thể được nhân bản hoặc biến mất. Bạn có nghĩ rằng có thể có bất kỳ kết nối?
glaebhoerl

14

Tôi thích nghĩ về Mũi tên, như Monads và Functor, khi cho phép lập trình viên thực hiện các tác phẩm kỳ lạ của các hàm.

Không có Monads hoặc Mũi tên (và Functor), thành phần các chức năng trong ngôn ngữ chức năng bị giới hạn trong việc áp dụng một chức năng cho kết quả của chức năng khác. Với monad và functor, bạn có thể xác định hai hàm và sau đó viết mã có thể sử dụng lại riêng biệt, xác định cách các hàm đó, trong ngữ cảnh của đơn nguyên cụ thể, tương tác với nhau và với dữ liệu được truyền vào chúng. Mã này được đặt trong mã liên kết của Monad. Vì vậy, một đơn nguyên là một chế độ xem, chỉ là một thùng chứa cho mã liên kết có thể sử dụng lại. Các chức năng sáng tác khác nhau trong bối cảnh của một đơn vị từ một đơn vị khác.

Một ví dụ đơn giản là đơn vị Có thể, trong đó có mã trong hàm liên kết sao cho nếu một hàm A được tạo với hàm B trong một đơn vị Có thể và B tạo ra Không có gì, thì mã liên kết sẽ đảm bảo rằng thành phần của hai hàm tạo ra Không có gì, mà không cần áp dụng A cho giá trị Không có gì từ B. Nếu không có đơn vị, lập trình viên sẽ phải viết mã vào A để kiểm tra đầu vào Không có gì.

Monads cũng có nghĩa là lập trình viên không cần phải nhập rõ ràng các tham số mà mỗi hàm yêu cầu vào mã nguồn - hàm liên kết xử lý việc truyền tham số. Vì vậy, bằng cách sử dụng các đơn vị, mã nguồn có thể bắt đầu trông giống như một chuỗi các tên hàm tĩnh, thay vì trông giống như hàm A "gọi" hàm B với các tham số C và D - mã bắt đầu giống như một mạch điện tử hơn là máy di chuyển - nhiều chức năng hơn bắt buộc.

Mũi tên cũng kết nối các chức năng với nhau bằng chức năng liên kết, cung cấp chức năng có thể sử dụng lại và ẩn các tham số. Nhưng chính Mũi tên có thể được kết nối với nhau và được soạn thảo và có thể tùy ý định tuyến dữ liệu đến các Mũi tên khác khi chạy. Bây giờ bạn có thể áp dụng dữ liệu cho hai đường dẫn của Mũi tên, "làm những việc khác nhau" cho dữ liệu và lắp lại kết quả. Hoặc bạn có thể chọn nhánh Mũi tên nào để truyền dữ liệu, tùy thuộc vào một số giá trị trong dữ liệu. Mã kết quả thậm chí giống như một mạch điện tử, với các công tắc, độ trễ, tích hợp, vv Chương trình trông rất tĩnh và bạn không thể thấy nhiều thao tác dữ liệu đang diễn ra. Có ngày càng ít tham số để suy nghĩ và ít cần suy nghĩ về những tham số giá trị nào có thể hoặc không thể thực hiện.

Viết chương trình Arrowized chủ yếu liên quan đến việc chọn ra các mũi tên như bộ chia, công tắc, độ trễ và bộ tích hợp, nâng các chức năng vào các Mũi tên đó và kết nối các Mũi tên với nhau để tạo thành Mũi tên lớn hơn. Trong Lập trình phản ứng chức năng mũi tên, Mũi tên tạo thành một vòng lặp, với đầu vào từ thế giới được kết hợp với đầu ra từ lần lặp cuối cùng của chương trình, để đầu ra phản ứng với đầu vào trong thế giới thực.

Một trong những giá trị thế giới thực là thời gian. Trong Yampa, Mũi tên Chức năng Tín hiệu vô hình xâu chuỗi tham số thời gian thông qua chương trình máy tính - bạn không bao giờ truy cập giá trị thời gian, nhưng nếu bạn kết nối một mũi tên tích hợp vào chương trình, nó sẽ xuất các giá trị được tích hợp theo thời gian mà bạn có thể sử dụng để chuyển đến mũi tên khác.


nhưng điều này nghe có vẻ như một functor ứng dụng (một số trình bao quanh một hàm, cung cấp các hàm trợ giúp, trong một số ngữ cảnh cụ thể, để sử dụng lại các hàm đã có cho các kiểu được bọc). Tôi chắc chắn cần đọc thêm để hiểu, nhưng có lẽ bạn có thể giúp đỡ, bằng cách chỉ ra những gì tôi đang thiếu
Belun

3

Chỉ là một bổ sung cho các câu trả lời khác: Cá nhân nó giúp tôi rất nhiều để hiểu khái niệm như vậy là gì (về mặt toán học) và làm thế nào nó liên quan đến các khái niệm khác mà tôi biết.

Trong trường hợp mũi tên, tôi thấy bài báo sau đây hữu ích - nó so sánh các đơn nguyên, functor ứng dụng (thành ngữ) và mũi tên: Thành ngữ là lãng quên, mũi tên là tỉ mỉ, các đơn vị được lăng nhăng bởi Sam Lindley, Philip Wadler và Jeremy Yallop.

Ngoài ra tôi tin rằng không ai đề cập đến liên kết này có thể cung cấp cho bạn một số ý tưởng và tài liệu về chủ đề này.

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.