Trong lập trình hàm, hàm functor là gì?


224

Tôi đã bắt gặp thuật ngữ 'Functor' một vài lần trong khi đọc các bài viết khác nhau về lập trình chức năng, nhưng các tác giả thường cho rằng người đọc đã hiểu thuật ngữ này. Nhìn quanh trên web đã cung cấp các mô tả kỹ thuật quá mức (xem bài viết Wikipedia ) hoặc các mô tả cực kỳ mơ hồ (xem phần về Functor tại trang web hướng dẫn ocaml này ).

Ai đó có thể vui lòng xác định thuật ngữ, giải thích việc sử dụng nó và có thể cung cấp một ví dụ về cách Functor được tạo và sử dụng không?

Chỉnh sửa : Trong khi tôi quan tâm đến lý thuyết đằng sau thuật ngữ này, tôi ít quan tâm đến lý thuyết hơn là tôi thực hiện và sử dụng thực tế khái niệm này.

Chỉnh sửa 2 : Có vẻ như có một số thuật ngữ chéo đang diễn ra: Tôi đặc biệt đề cập đến Functor của lập trình chức năng, không phải là các đối tượng chức năng của C ++.




Nếu bạn quan tâm đến việc triển khai và sử dụng thực tế hơn là về thuật ngữ và lý thuyết địa tầng đằng sau khái niệm này, bạn chỉ cần một lớp lót: một functor trưng ra chức năng "bản đồ".
Richard Gomes

@RichardGomes IMHO Tôi nghĩ rằng nó làm giảm vai trò của functor đối với một giao diện giống như Java đơn giản, nhưng thực tế không phải vậy. Một functor biến đổi các công cụ, nó xây dựng các loại mới từ các loại hiện có (trong Haskell) có nghĩa là các loại cũng được ánh xạ. fmapánh xạ các chức năng. Có hai loại ánh xạ liên quan. Cách nhìn nhận đó sẽ giúp hiểu lý thuyết phạm trù (mà khái quát hơn). Ý tôi là thật thú vị khi hiểu lý thuyết thể loại cơ bản để giúp chúng tôi với tất cả các công cụ lý thuyết thể loại trong Haskell (functor, monads, ...).
Ludovic Kuty

@VladtheImpala Bài đăng trên blog rất tuyệt vời, nhưng ngay cả khi nó giúp ích rất nhiều, tôi muốn ghi nhớ rằng một functor xây dựng (ánh xạ tới) một loại khác. Tôi đặc biệt thích câu "Một functor F lấy mỗi loại T và ánh xạ nó sang FT loại mới" trong Monads giống như burritos . IMHO nó không chỉ là một bối cảnh (một hộp) xung quanh một giá trị ngay cả khi điều đó chứng tỏ thực tế để thấy những thứ như thế này (Haskell PoV so với lý thuyết thể loại PoV?)
Ludovic Kuty

Câu trả lời:


273

Từ "functor" xuất phát từ lý thuyết thể loại, là một nhánh rất chung chung, rất trừu tượng của toán học. Nó đã được các nhà thiết kế của các ngôn ngữ chức năng mượn theo ít nhất hai cách khác nhau.

  • Trong họ ngôn ngữ ML, functor là một mô đun lấy một hoặc nhiều mô đun khác làm tham số. Nó được coi là một tính năng nâng cao và hầu hết các lập trình viên mới bắt đầu gặp khó khăn với nó.

    Như một ví dụ về triển khai và sử dụng thực tế, bạn có thể xác định dạng cây tìm kiếm nhị phân cân bằng yêu thích của mình một lần và mãi mãi là một functor, và nó sẽ lấy như một tham số mà một mô-đun cung cấp:

    • Loại khóa được sử dụng trong cây nhị phân

    • Hàm tổng thứ tự trên các phím

    Khi bạn đã hoàn thành việc này, bạn có thể sử dụng cùng một triển khai cây nhị phân cân bằng. (Loại giá trị được lưu trữ trong cây thường là đa hình trái cây, cây không cần nhìn vào các giá trị ngoài việc sao chép chúng xung quanh, trong khi đó cây chắc chắn cần có thể so sánh các khóa và nó có chức năng so sánh từ tham số của functor.)

    Một ứng dụng khác của ML functor là các giao thức mạng được xếp lớp . Liên kết là một bài báo thực sự tuyệt vời của nhóm CMU Fox; nó chỉ ra cách sử dụng functor để xây dựng các lớp giao thức phức tạp hơn (như TCP) trên loại lớp đơn giản hơn (như IP hoặc thậm chí trực tiếp qua Ethernet). Mỗi lớp được thực hiện như một functor lấy tham số là lớp bên dưới nó. Cấu trúc của phần mềm thực sự phản ánh cách mọi người nghĩ về vấn đề, trái ngược với các lớp chỉ tồn tại trong tâm trí của lập trình viên. Năm 1994 khi tác phẩm này được xuất bản, nó là một vấn đề lớn.

    Đối với một ví dụ hoang dã về ML functor đang hoạt động, bạn có thể thấy ML Module Mania bằng giấy , trong đó có một ví dụ có thể xuất bản (nghĩa là đáng sợ) của functor tại nơi làm việc. Để có một lời giải thích rõ ràng, rõ ràng, rõ ràng về hệ thống mô-đun ML (so sánh với các loại mô-đun khác), hãy đọc một vài trang đầu tiên của Các loại biểu hiện, mô-đun POPL xuất sắc năm 1994 của Xavier Leroy .

  • Trong Haskell, và trong một số ngôn ngữ chức năng thuần túy có liên quan, Functorlà một lớp loại . Một loại thuộc về một loại loại (hoặc kỹ thuật hơn, loại "là một thể hiện của" loại loại) khi loại cung cấp các hoạt động nhất định với hành vi dự kiến ​​nhất định. Một loại Tcó thể thuộc về lớp Functornếu nó có hành vi giống như bộ sưu tập nhất định:

    • Loại Tđược tham số hóa trên loại khác, mà bạn nên nghĩ là loại phần tử của bộ sưu tập. Các loại của bộ sưu tập đầy đủ là sau đó một cái gì đó giống như T Int, T String, T Bool, nếu bạn đang chứa số nguyên, chuỗi, hoặc Booleans tương ứng. Nếu loại phần tử không xác định, nó được viết dưới dạng tham số loại a , như trong T a.

      Ví dụ bao gồm danh sách (không hoặc nhiều phần tử loại a), Maybeloại (không hoặc một phần tử loại a), bộ phần tử loại a, mảng phần tử loại a, tất cả các loại cây tìm kiếm chứa giá trị loại avà nhiều loại khác bạn có thể nghĩ ra.

    • Một thuộc tính khác Tphải thỏa mãn là nếu bạn có một hàm kiểu a -> b(một hàm trên các phần tử), thì bạn phải có khả năng lấy hàm đó và tạo ra một hàm liên quan trên các bộ sưu tập. Bạn làm điều này với toán tử fmap, được chia sẻ bởi mọi loại trong Functorlớp loại. Toán tử thực sự bị quá tải, vì vậy nếu bạn có một hàm evenvới kiểu Int -> Bool, thì

      fmap even

      là một chức năng quá tải có thể làm nhiều điều tuyệt vời:

      • Chuyển đổi danh sách các số nguyên thành danh sách Booleans

      • Chuyển đổi một cây số nguyên thành cây Booleans

      • Chuyển đổi Nothingsang NothingJust 7sangJust False

      Trong Haskell, thuộc tính này được thể hiện bằng cách đưa ra loại fmap:

      fmap :: (Functor t) => (a -> b) -> t a -> t b

      bây giờ chúng ta có một cái nhỏ t, có nghĩa là "bất kỳ loại nào trong Functorlớp."

    Để làm cho một câu chuyện dài trở nên ngắn gọn, trong Haskell, functor là một loại bộ sưu tập mà nếu bạn được cung cấp một hàm trên các phần tử, fmapsẽ cung cấp cho bạn một hàm trên các bộ sưu tập . Như bạn có thể tưởng tượng, đây là một ý tưởng có thể được tái sử dụng rộng rãi, đó là lý do tại sao nó được ban phước như một phần của thư viện tiêu chuẩn của Haskell.

Như thường lệ, mọi người tiếp tục phát minh ra những bản tóm tắt mới, hữu ích và bạn có thể muốn xem xét các hàm xử lý ứng dụng , trong đó tài liệu tham khảo tốt nhất có thể là một bài báo gọi là Lập trình ứng dụng với Hiệu ứng của Conor McBride và Ross Paterson.


7
Tôi hiểu cả ML-functor và Haskell-functor, nhưng thiếu cái nhìn sâu sắc để liên kết chúng lại với nhau. Mối quan hệ giữa hai điều này, trong một ý nghĩa lý thuyết thể loại là gì?
Wei Hu

6
@Wei Hu: Lý thuyết danh mục chưa bao giờ có ý nghĩa với tôi. Điều tốt nhất tôi có thể nói là cả ba khái niệm đều liên quan đến ánh xạ.
Norman Ramsey

16
Theo wiki Haskell này: en.wikibooks.org/wiki/Haskell/Carget_theory , nó giống như thế này: Một thể loại là một tập hợp các đối tượng và hình thái (chức năng), trong đó các hình thái là từ các đối tượng trong một thể loại đến các đối tượng khác trong thể loại đó . Functor là một chức năng ánh xạ các đối tượng và hình thái từ một loại sang các đối tượng và hình thái trong một loại khác. Ít nhất đó là cách tôi hiểu nó. Điều đó có nghĩa là chính xác cho lập trình tôi vẫn chưa hiểu.
paul

5
@ Norman-ramsey, bạn đã xem Toán học khái niệm của Lawvere và Schanuel chưa? Tôi là một người mới hoàn toàn trong khu vực nhưng cuốn sách rất dễ đọc và - dám nói - thú vị. (Yêu lời giải thích của bạn.)
Ram Rajamony

2
then you have to be able to take that function and product a related function on collectionsÝ của bạn là producethay vì product?
vấn đề

64

Các câu trả lời khác ở đây đã hoàn tất, nhưng tôi sẽ thử một lời giải thích khác về việc sử dụng functor của FP . Hãy coi điều này là tương tự:

Một functor là một thùng chứa loại a , khi chịu một chức năng ánh xạ từ ab , sẽ tạo ra một vật chứa loại b .

Không giống như sử dụng con trỏ trừu tượng hóa chức năng trong C ++, ở đây functor không phải là hàm; đúng hơn, đó là một cái gì đó hoạt động nhất quán khi chịu một chức năng .


3
Một container loại b có nghĩa là "cùng một loại container với container đầu vào, nhưng bây giờ chứa đầy b". Vì vậy, nếu chúng ta có một danh sách chuối, và chúng ta lập bản đồ một chức năng lấy một quả chuối và đưa ra một món salad trái cây, bây giờ chúng ta có một danh sách các món salad trái cây. Tương tự như vậy, nếu chúng ta có một cây chuối và chúng ta lập bản đồ chức năng tương tự, bây giờ chúng ta sẽ có một cây táo. Vv câydanh sách là hai Functor ở đây.
Qqwy

3
"Một functor là một thùng chứa loại a, khi chịu một chức năng" - nó thực sự là một cách khác - chức năng (hình thái) phải chịu một functor, được ánh xạ vào một hình thái khác
Dmitri Zaitsev

38

Có ba ý nghĩa khác nhau, không liên quan nhiều!

  • Trong Ocaml, nó là một mô-đun tham số. Xem hướng dẫn . Tôi nghĩ rằng cách tốt nhất để mò mẫm chúng là ví dụ: (viết nhanh, có thể là lỗi)

    module type Order = sig
        type t
        val compare: t -> t -> bool
    end;;
    
    
    module Integers = struct
        type t = int
        let compare x y = x > y
    end;;
    
    module ReverseOrder = functor (X: Order) -> struct
        type t = X.t
        let compare x y = X.compare y x
    end;;
    
    (* We can order reversely *)
    module K = ReverseOrder (Integers);;
    Integers.compare 3 4;;   (* this is false *)
    K.compare 3 4;;          (* this is true *)
    
    module LexicographicOrder = functor (X: Order) -> 
      functor (Y: Order) -> struct
        type t = X.t * Y.t
        let compare (a,b) (c,d) = if X.compare a c then true
                             else if X.compare c a then false
                             else Y.compare b d
    end;;
    
    (* compare lexicographically *)
    module X = LexicographicOrder (Integers) (Integers);;
    X.compare (2,3) (4,5);;
    
    module LinearSearch = functor (X: Order) -> struct
        type t = X.t array
        let find x k = 0 (* some boring code *)
    end;;
    
    module BinarySearch = functor (X: Order) -> struct
        type t = X.t array
        let find x k = 0 (* some boring code *)
    end;;
    
    (* linear search over arrays of integers *)
    module LS = LinearSearch (Integers);;
    LS.find [|1;2;3] 2;;
    (* binary search over arrays of pairs of integers, 
       sorted lexicographically *)
    module BS = BinarySearch (LexicographicOrder (Integers) (Integers));;
    BS.find [|(2,3);(4,5)|] (2,3);;

Bây giờ bạn có thể thêm nhanh chóng nhiều đơn hàng có thể, cách để tạo đơn hàng mới, thực hiện tìm kiếm nhị phân hoặc tuyến tính dễ dàng hơn chúng. Lập trình chung FTW.

  • Trong các ngôn ngữ lập trình chức năng như Haskell, nó có nghĩa là một số hàm tạo kiểu (các kiểu tham số hóa như danh sách, bộ) có thể được "ánh xạ". Nói chính xác, một functor fđược trang bị (a -> b) -> (f a -> f b). Điều này có nguồn gốc trong lý thuyết thể loại. Bài viết Wikipedia bạn liên kết đến là cách sử dụng này.

    class Functor f where
        fmap :: (a -> b) -> (f a -> f b)
    
    instance Functor [] where      -- lists are a functor
        fmap = map
    
    instance Functor Maybe where   -- Maybe is option in Haskell
        fmap f (Just x) = Just (f x)
        fmap f Nothing = Nothing
    
    fmap (+1) [2,3,4]   -- this is [3,4,5]
    fmap (+1) (Just 5)  -- this is Just 6
    fmap (+1) Nothing   -- this is Nothing

Vì vậy, đây là một loại đặc biệt của một nhà xây dựng kiểu, và ít liên quan đến functor trong Ocaml!

  • Trong các ngôn ngữ bắt buộc, nó là một con trỏ để hoạt động.

Không nên <q> map </ q> trong 3 dòng cuối cùng của nhận xét này có thực sự là <q> fmap </ q> không?
imz - Ivan Zakharyaschev

1
Tôi đã luôn luôn đọc rằng functor là container - nhưng đây chỉ là một sự đơn giản hóa kém. Câu trả lời của bạn cuối cùng đã cung cấp liên kết còn thiếu: Functor là một lớp loại (ràng buộc kiểu) cho các kiểu tham số (hàm tạo kiểu). Nó đơn giản mà!

16

Trong OCaml, nó là một mô-đun tham số.

Nếu bạn biết C ++, hãy nghĩ về một functor OCaml như một khuôn mẫu. C ++ chỉ có các mẫu lớp và functor hoạt động ở quy mô mô-đun.

Một ví dụ về functor là Map.Make; module StringMap = Map.Make (String);;xây dựng một mô-đun bản đồ hoạt động với các bản đồ có chuỗi.

Bạn không thể đạt được thứ gì đó như StringMap chỉ với đa hình; bạn cần phải thực hiện một số giả định về các phím. Mô-đun Chuỗi chứa các hoạt động (so sánh, v.v.) trên một loại chuỗi hoàn toàn theo thứ tự và functor sẽ liên kết với các hoạt động mà mô-đun Chuỗi chứa. Bạn có thể làm một cái gì đó tương tự với lập trình hướng đối tượng, nhưng bạn có chi phí không định hướng phương pháp.


Tôi đã nhận được điều đó từ trang web ocaml - nhưng tôi không hiểu việc sử dụng một mô-đun tham số sẽ là gì.
Erik Forbes

4
@Kornel Vâng, những gì tôi mô tả là một khái niệm OCaml. Khái niệm còn lại chỉ là giá trị chức năng của người Viking, không có gì đặc biệt trong FP. @Erik Tôi mở rộng một chút, nhưng tài liệu tham khảo tải chậm.
Tobu

13

Bạn có khá nhiều câu trả lời hay. Tôi sẽ tham gia:

Functor, theo nghĩa toán học, là một loại hàm đặc biệt trên đại số. Đây là một hàm tối thiểu ánh xạ một đại số sang một đại số khác. "Tối thiểu" được thể hiện bởi luật functor.

Có hai cách để xem xét điều này. Ví dụ, danh sách là functor trên một số loại. Nghĩa là, được đưa ra một đại số trên một loại 'a', bạn có thể tạo ra một đại số tương thích của danh sách có chứa những thứ thuộc loại 'a'. (Ví dụ: bản đồ đưa một phần tử vào danh sách đơn có chứa nó: f (a) = [a]) Một lần nữa, khái niệm tương thích được thể hiện bởi luật functor.

Mặt khác, với một functor f "over" một loại a, (nghĩa là fa là kết quả của việc áp dụng functor f cho đại số loại a) và hàm từ g: a -> b, chúng ta có thể tính toán một functor mới F = (fmap g) ánh xạ fa đến f b. Nói tóm lại, fmap là một phần của F ánh xạ "các phần functor" thành "các phần functor" và g là một phần của hàm ánh xạ "các phần đại số" thành "các phần đại số". Nó có một chức năng, một functor và sau khi hoàn thành, nó cũng là một functor.

Có vẻ như các ngôn ngữ khác nhau đang sử dụng các khái niệm khác nhau của functor, nhưng chúng không phải là ngôn ngữ. Họ chỉ đơn thuần sử dụng functor trên các đại số khác nhau. OCamls có một đại số của các mô-đun và functor trên đại số đó cho phép bạn đính kèm các khai báo mới vào một mô-đun theo cách "tương thích".

Một functor Haskell KHÔNG phải là một lớp loại. Nó là một kiểu dữ liệu với một biến miễn phí thỏa mãn lớp kiểu. Nếu bạn sẵn sàng đào sâu vào ruột của một kiểu dữ liệu (không có biến miễn phí), bạn có thể diễn giải lại một kiểu dữ liệu dưới dạng hàm functor qua đại số cơ bản. Ví dụ:

dữ liệu F = F Int

đẳng cấu với lớp Ints. Vì vậy, F, như một hàm tạo giá trị, là một hàm ánh xạ Int đến F Int, một đại số tương đương. Nó là một functor. Mặt khác, bạn không nhận được fmap miễn phí tại đây. Đó là những gì phù hợp với mô hình là dành cho.

Functor rất tốt cho việc "gắn" mọi thứ vào các yếu tố của đại số, theo cách tương thích đại số.


8

Câu trả lời tốt nhất cho câu hỏi đó được tìm thấy trong "typeclassopedia" của Brent Yorgey.

Vấn đề này của Monad Reader chứa một định nghĩa chính xác về chức năng của functor cũng như nhiều định nghĩa về các khái niệm khác cũng như sơ đồ. (Monoid, Applicative, Monad và các khái niệm khác được giải thích và nhìn thấy liên quan đến một functor).

http://haskell.org/sitewiki/images/8/85/TMR-Issue13.pdf

trích từ typeclassopedia cho Functor: "Một trực giác đơn giản là một Functor đại diện cho một container container của một loại nào đó, cùng với khả năng áp dụng một chức năng thống nhất cho mọi yếu tố trong container"

Nhưng thực sự toàn bộ typeclassopedia là một cách đọc rất được khuyến khích mà dễ dàng đáng ngạc nhiên. Theo một cách nào đó, bạn có thể thấy kiểu chữ được trình bày ở đó như một mẫu song song để thiết kế mẫu trong đối tượng theo nghĩa là chúng cung cấp cho bạn một từ vựng cho hành vi hoặc khả năng nhất định.

Chúc mừng


7

Có một ví dụ khá hay trong cuốn sách O'Reilly OCaml trên trang web của Inria (điều này không may viết ra). Tôi tìm thấy một ví dụ rất giống trong cuốn sách này được sử dụng bởi caltech: Giới thiệu về OCaml (liên kết pdf) . Phần có liên quan là chương về functor (Trang 139 trong cuốn sách, trang 149 trong PDF).

Trong cuốn sách, họ có một functor tên là Make Set tạo ra cấu trúc dữ liệu bao gồm một danh sách và các hàm để thêm một phần tử, xác định xem một phần tử có trong danh sách hay không và để tìm phần tử. Hàm so sánh được sử dụng để xác định xem nó có trong / không trong tập hợp đã được tham số hóa hay không (đó là điều làm cho Make Set trở thành một functor thay vì mô-đun).

Họ cũng có một mô-đun thực hiện chức năng so sánh để nó thực hiện so sánh chuỗi không nhạy.

Sử dụng functor và mô-đun thực hiện so sánh, họ có thể tạo một mô-đun mới trong một dòng:

module SSet = MakeSet(StringCaseEqual);;

mà tạo ra một mô-đun cho một cấu trúc dữ liệu tập hợp sử dụng các so sánh không phân biệt chữ hoa chữ thường. Nếu bạn muốn tạo một tập hợp sử dụng so sánh phân biệt chữ hoa chữ thường thì bạn chỉ cần thực hiện một mô-đun so sánh mới thay vì mô-đun cấu trúc dữ liệu mới.

Tobu đã so sánh functor với các mẫu trong C ++ mà tôi nghĩ là khá thích hợp.


6

Đưa ra các câu trả lời khác và những gì tôi sẽ đăng bây giờ, tôi nói rằng đó là một từ quá tải, nhưng dù sao ...

Để biết gợi ý về ý nghĩa của từ 'functor' trong Haskell, hãy hỏi GHCi:

Prelude> :info Functor
class Functor f where
  fmap :: forall a b. (a -> b) -> f a -> f b
  (GHC.Base.<$) :: forall a b. a -> f b -> f a
        -- Defined in GHC.Base
instance Functor Maybe -- Defined in Data.Maybe
instance Functor [] -- Defined in GHC.Base
instance Functor IO -- Defined in GHC.Base

Vì vậy, về cơ bản, một functor trong Haskell là thứ có thể được ánh xạ. Một cách khác để nói rằng functor là thứ có thể được coi là một thùng chứa có thể được yêu cầu sử dụng một hàm nhất định để biến đổi giá trị mà nó chứa; do đó, cho các danh sách, fmaptrùng hợp với map, cho Maybe, fmap f (Just x) = Just (f x), fmap f Nothing = Nothing, vv

Phần phụ kiểu chữ Functor và phần trên Functor, Functor ứng dụng và Monoids of Learn You a Haskell for Great Good đưa ra một số ví dụ về khái niệm cụ thể này hữu ích. (Tóm tắt: rất nhiều nơi! :-))

Lưu ý rằng bất kỳ đơn nguyên nào cũng có thể được coi là một functor, và trên thực tế, như Craig Stuntz chỉ ra, các functor thường được sử dụng nhất có xu hướng là các đơn vị ... OTOH, đôi khi rất thuận tiện để tạo một kiểu mẫu của kiểu chữ Functor mà không đi đến những rắc rối để làm cho nó một Monad. (Ví dụ: trong trường hợp ZipListtừ Control.Applicative, được đề cập trên một trong những trang đã nói ở trên .)


5

Đây là một bài viết về functor từ một POV lập trình , tiếp theo là cụ thể hơn cách chúng thể hiện trong các ngôn ngữ lập trình .

Việc sử dụng thực tế của một functor là trong một đơn nguyên, và bạn có thể tìm thấy nhiều hướng dẫn về các đơn nguyên nếu bạn tìm kiếm điều đó.


1
"Việc sử dụng thực tế của một functor là trong một đơn nguyên" - không chỉ. Tất cả các đơn nguyên là functor, nhưng có rất nhiều cách sử dụng cho functor không monad.
amindfv

1
Tôi muốn nói rằng việc nghiên cứu các đơn vị để sử dụng functor giống như tiết kiệm cho một chiếc Rolls để đi mua đồ tạp hóa.
Marco Faustinelli

5

Trong một bình luận cho câu trả lời được bình chọn hàng đầu , người dùng Wei Hu hỏi:

Tôi hiểu cả ML-functor và Haskell-functor, nhưng thiếu cái nhìn sâu sắc để liên kết chúng lại với nhau. Mối quan hệ giữa hai điều này, trong một ý nghĩa lý thuyết thể loại là gì?

Lưu ý : Tôi không biết ML, vì vậy xin vui lòng tha thứ và sửa chữa mọi lỗi liên quan.

Ban đầu, giả sử rằng tất cả chúng ta đều quen thuộc với các định nghĩa về 'thể loại' và 'functor'.

Một câu trả lời nhỏ gọn sẽ là "Haskell-functor" là functor (endo-) F : Hask -> Hasktrong khi "ML-functor" là functor G : ML -> ML'.

Ở đây, Hasklà thể loại được hình thành bởi các loại và hàm Haskell giữa chúng, và tương tự MLML'là các thể loại được xác định bởi các cấu trúc ML.

Lưu ý : Có một số vấn đề kỹ thuật với việc tạo Haskmột danh mục, nhưng có nhiều cách xung quanh chúng.

Từ góc độ lý thuyết thể loại, điều này có nghĩa là Hask-functor là bản đồ Fcủa các loại Haskell:

data F a = ...

cùng với bản đồ fmapcác chức năng của Haskell:

instance Functor F where
    fmap f = ...

ML khá giống nhau, mặc dù tôi không fmapbiết về sự trừu tượng hóa kinh điển , vì vậy hãy xác định một:

signature FUNCTOR = sig
  type 'a f
  val fmap: 'a -> 'b -> 'a f -> 'b f
end

Đó là fbản đồ ML-types và chức năng fmapbản đồ ML, vì vậy

functor StructB (StructA : SigA) :> FUNCTOR =
struct
  fmap g = ...
  ...
end

là một functor F: StructA -> StructB.


5

"Functor đang lập bản đồ của các đối tượng và hình thái bảo tồn thành phần và bản sắc của một thể loại."

Hãy xác định một thể loại là gì?

Đó là một loạt các đối tượng!

Vẽ một vài dấu chấm (bây giờ là 2 chấm, một là 'a' khác là 'b') bên trong một vòng tròn và đặt tên cho vòng tròn A (Danh mục) bây giờ.

Các hạng mục giữ gì?

Thành phần giữa các đối tượng và chức năng Nhận dạng cho mọi đối tượng.

Vì vậy, chúng ta phải ánh xạ các đối tượng và bảo tồn thành phần sau khi áp dụng Functor của chúng ta.

Hãy hình dung 'A' là danh mục của chúng tôi có các đối tượng ['a', 'b'] và tồn tại một hình thái a -> b

Bây giờ, chúng ta phải định nghĩa một functor có thể ánh xạ các đối tượng và hình thái này vào một loại khác 'B'.

Hãy nói rằng functor được gọi là 'Có thể'

data Maybe a = Nothing | Just a

Vì vậy, Danh mục 'B' trông như thế này.

Vui lòng vẽ một vòng tròn khác nhưng lần này với 'Có thể là' và 'Có thể b' thay vì 'a' và 'b'.

Mọi thứ có vẻ tốt và tất cả các đối tượng được ánh xạ

'a' trở thành 'Có thể a' và 'b' trở thành 'Có thể b'.

Nhưng vấn đề là chúng ta phải lập bản đồ hình thái từ 'a' đến 'b'.

Điều đó có nghĩa là hình thái a -> b trong 'A' nên ánh xạ tới hình thái 'Có thể là một' -> 'Có thể b'

morphism từ a -> b được gọi là f, sau đó morphism từ 'Có thể a' -> 'Có thể b' được gọi là 'fmap f'

Bây giờ hãy xem chức năng 'f' đã làm trong 'A' và xem liệu chúng ta có thể sao chép nó trong 'B' không

định nghĩa hàm của 'f' trong 'A':

f :: a -> b

f lấy a và trả về b

định nghĩa hàm của 'f' trong 'B':

f :: Maybe a -> Maybe b

f mất Có thể a và trở về Có thể b

hãy xem cách sử dụng fmap để ánh xạ hàm 'f' từ 'A' sang hàm 'fmap f' trong 'B'

định nghĩa của fmap

fmap :: (a -> b) -> (Maybe a -> Maybe b)
fmap f Nothing = Nothing
fmap f (Just x) = Just(f x)

Vậy, chúng ta đang làm gì ở đây?

Chúng tôi đang áp dụng hàm 'f' cho 'x', loại 'a'. Kết hợp mẫu đặc biệt của "Không có gì" xuất phát từ định nghĩa của Functor Maybe.

Vì vậy, chúng tôi đã ánh xạ các đối tượng của mình [a, b] và hình thái [f] từ loại 'A' sang loại 'B'.

Đó là Functor!

nhập mô tả hình ảnh ở đây


Câu trả lời thú vị. Tôi muốn bổ sung nó với Monads giống như burritos (câu trả lời hài hước cho Trừu tượng hóa, trực giác và hướng dẫn sử dụng đơn vị hướng dẫn của Monad ) và câu "A functor F lấy mỗi loại T và ánh xạ nó sang loại FT mới" . Lập trình chức năng và lý thuyết danh mục - Thể loại và Functor cũng hữu ích.
Ludovic Kuty

3

Tổng quan thô

Trong lập trình chức năng, functor về cơ bản là một cấu trúc nâng các hàm unary thông thường (tức là các hàm có một đối số) thành các hàm giữa các biến của các kiểu mới. Việc viết và duy trì các hàm đơn giản giữa các đối tượng đơn giản sẽ dễ dàng hơn nhiều và sử dụng hàm functor để nâng chúng, sau đó viết thủ công các hàm giữa các đối tượng chứa phức tạp. Ưu điểm hơn nữa là chỉ viết các hàm đơn giản một lần và sau đó sử dụng lại chúng thông qua các hàm xử lý khác nhau.

Ví dụ về functor bao gồm mảng, "có thể" và "hoặc" functor, tương lai (xem ví dụ https://github.com/Avaq/Fluture ) và nhiều thứ khác.

Hình minh họa

Hãy xem xét chức năng xây dựng tên của người đầy đủ từ tên và họ. Chúng ta có thể định nghĩa nó giống fullName(firstName, lastName)như chức năng của hai đối số, tuy nhiên sẽ không phù hợp với các hàm xử lý chỉ xử lý các chức năng của một đối số. Để khắc phục, chúng tôi thu thập tất cả các đối số trong một đối tượng name, giờ đây trở thành đối số duy nhất của hàm:

// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName

Bây giờ nếu chúng ta có nhiều người trong một mảng thì sao? Thay vì tự đi qua danh sách, chúng ta chỉ cần sử dụng lại hàm của mình fullNamethông qua mapphương thức được cung cấp cho các mảng với một dòng mã ngắn:

fullNameList = nameList => nameList.map(fullName)

và sử dụng nó như

nameList = [
    {firstName: 'Steve', lastName: 'Jobs'},
    {firstName: 'Bill', lastName: 'Gates'}
]

fullNames = fullNameList(nameList) 
// => ['Steve Jobs', 'Bill Gates']

Điều đó sẽ hoạt động, bất cứ khi nào mỗi mục trong chúng tôi nameListlà một đối tượng cung cấp cả hai firstNamelastNamethuộc tính. Nhưng nếu một số đối tượng không (hoặc thậm chí không phải là đối tượng) thì sao? Để tránh các lỗi và làm cho mã an toàn hơn, chúng ta có thể bọc các đối tượng của mình thành Maybeloại (ví dụ: https : //sanc Sanctuary.js.org/#maybe-type ):

// function to test name for validity
isValidName = name => 
    (typeof name === 'object') 
    && (typeof name.firstName === 'string')
    && (typeof name.lastName === 'string')

// wrap into the Maybe type
maybeName = name => 
    isValidName(name) ? Just(name) : Nothing()

trong đó Just(name)một container chỉ chứa các tên hợp lệ và Nothing()là giá trị đặc biệt được sử dụng cho mọi thứ khác. Bây giờ thay vì ngắt (hoặc quên) để kiểm tra tính hợp lệ của các đối số của chúng tôi, chúng tôi chỉ có thể sử dụng lại (nâng) fullNamehàm ban đầu của chúng tôi bằng một dòng mã khác, dựa trên mapphương thức, lần này được cung cấp cho loại Có thể:

// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)

và sử dụng nó như

justSteve = maybeName(
    {firstName: 'Steve', lastName: 'Jobs'}
) // => Just({firstName: 'Steve', lastName: 'Jobs'})

notSteve = maybeName(
    {lastName: 'SomeJobs'}
) // => Nothing()

steveFN = maybeFullName(justSteve)
// => Just('Steve Jobs')

notSteveFN = maybeFullName(notSteve)
// => Nothing()

Lý thuyết danh mục

Một Functor trong Lý thuyết Danh mục là một bản đồ giữa hai loại tôn trọng thành phần hình thái của chúng. Trong Ngôn ngữ máy tính , Danh mục quan tâm chính là đối tượngđối tượngcác loại (bộ giá trị nhất định) và hình thái của chúng là các chức năng f:a->btừ loại này asang loại khác b.

Ví dụ, lấy aStringloại, bloại Số và flà hàm ánh xạ một chuỗi thành độ dài của nó:

// f :: String -> Number
f = str => str.length

Ở đây a = Stringđại diện cho tập hợp của tất cả các chuỗi và b = Numbertập hợp của tất cả các số. Theo nghĩa đó, cả hai abđại diện cho các đối tượng trong Danh mục tập hợp (liên quan chặt chẽ đến danh mục các loại, với sự khác biệt là không cần thiết ở đây). Trong Danh mục nhóm, hình thái giữa hai bộ chính xác là tất cả các chức năng từ bộ thứ nhất đến bộ thứ hai. Vì vậy, hàm độ dài của chúng ta fở đây là một hình thái từ tập hợp chuỗi thành tập hợp số.

Vì chúng ta chỉ xem xét phạm trù tập hợp, các Functor có liên quan từ chính nó vào bản đồ là các bản đồ gửi các đối tượng đến các đối tượng và hình thái đến các hình thái, đáp ứng các định luật đại số nhất định.

Thí dụ: Array

Arraycó thể có nghĩa là nhiều thứ, nhưng chỉ có một thứ là Functor - kiểu xây dựng, ánh xạ một kiểu athành kiểu [a]của tất cả các mảng kiểu a. Ví dụ, Arrayfunctor ánh xạ loại String thành loại [String](tập hợp tất cả các mảng của chuỗi có độ dài tùy ý) và đặt loại Numberthành loại tương ứng [Number](tập hợp tất cả các mảng số).

Điều quan trọng là không nhầm lẫn bản đồ Functor

Array :: a => [a]

với một hình thái a -> [a]. Functor chỉ đơn giản là ánh xạ (liên kết) loại athành loại [a]như một thứ khác. Rằng mỗi loại thực sự là một tập hợp các yếu tố, không liên quan ở đây. Ngược lại, một hình thái là một chức năng thực tế giữa các bộ đó. Ví dụ, có một hình thái tự nhiên (chức năng)

pure :: a -> [a]
pure = x => [x]

sẽ gửi một giá trị vào mảng 1 phần tử với giá trị đó dưới dạng một mục nhập. Chức năng đó không phải là một phần của ArrayFunctor! Theo quan điểm của functor này, purechỉ là một chức năng như bất kỳ khác, không có gì đặc biệt.

Mặt khác, ArrayFunctor có phần thứ hai - phần hình thái. Mà ánh xạ một hình thái f :: a -> bthành một hình thái [f] :: [a] -> [b]:

// a -> [a]
Array.map(f) = arr => arr.map(f)

Đây arrlà bất kỳ mảng nào có độ dài tùy ý với các giá trị của loại aarr.map(f)là mảng có cùng độ dài với các giá trị của loại b, có các mục nhập là kết quả của việc áp dụng fcho các mục nhập của arr. Để làm cho nó trở thành một functor, các định luật toán học về ánh xạ nhận dạng đến nhận dạng và các tác phẩm thành các tác phẩm phải được giữ, rất dễ kiểm tra trong Arrayví dụ này .


2

Không mâu thuẫn với các câu trả lời lý thuyết hoặc toán học trước đó, nhưng Functor cũng là một Đối tượng (trong ngôn ngữ lập trình hướng đối tượng) chỉ có một phương thức và được sử dụng hiệu quả như một hàm.

Một ví dụ là giao diện Runnable trong Java, chỉ có một phương thức: run.

Hãy xem xét ví dụ này, đầu tiên trong Javascript, có chức năng hạng nhất:

[1, 2, 5, 10].map(function(x) { return x*x; });

Đầu ra: [1, 4, 25, 100]

Phương thức ánh xạ nhận một hàm và trả về một mảng mới với mỗi phần tử là kết quả của ứng dụng của hàm đó với giá trị tại cùng một vị trí trong mảng ban đầu.

Để làm điều tương tự là Java, sử dụng Functor, trước tiên bạn cần xác định một giao diện, giả sử:

public interface IntMapFunction {
  public int f(int x);
}

Sau đó, nếu bạn thêm một lớp bộ sưu tập có chức năng bản đồ, bạn có thể làm:

myCollection.map(new IntMapFunction() { public int f(int x) { return x * x; } });

Điều này sử dụng một lớp con nội tuyến của IntMapFunction để tạo Functor, tương đương với OO của hàm từ ví dụ JavaScript trước đó.

Sử dụng Functor cho phép bạn áp dụng các kỹ thuật chức năng bằng ngôn ngữ OO. Tất nhiên, một số ngôn ngữ OO cũng có hỗ trợ trực tiếp cho các chức năng, vì vậy điều này không bắt buộc.

Tham khảo: http://en.wikipedia.org/wiki/Function_object


Trên thực tế "đối tượng hàm" không phải là một mô tả chính xác của functor. Ví dụ, Arraylà một functor, nhưng Array(value)chỉ cung cấp các mảng 1 phần tử.
Dmitri Zaitsev

0

KISS: Một functor là một đối tượng có phương thức bản đồ.

Mảng trong JavaScript thực hiện bản đồ và do đó là functor. Hứa hẹn, Luồng và Cây thường thực hiện bản đồ bằng các ngôn ngữ chức năng và khi thực hiện, chúng được coi là functor. Phương thức bản đồ của functor lấy nội dung của chính nó và biến đổi từng phần trong số chúng bằng cách sử dụng hàm gọi lại chuyển đổi được truyền vào bản đồ và trả về một functor mới, chứa cấu trúc như functor đầu tiên, nhưng với các giá trị được chuyển đổi.

src: https://www.youtube.com/watch?v=DisD9ftUyCk&feature=youtu.be&t=76


1
Với lưu ý bên lề rằng 'đối tượng' nên được sử dụng rất rộng và chỉ có nghĩa là 'một cái gì đó'. Ví dụ, đối với các ngôn ngữ OOP, đối tượng thay thế cho lớp . Mọi người có thể nói 'functor là một lớp thực hiện giao diện Functor' (Tất nhiên, giao diện này có thể không có ở đó, nhưng bạn có thể nâng logic 'map' ra giao diện đó và có tất cả các lớp có thể ánh xạ của bạn chia sẻ nó - miễn là hệ thống loại của bạn cho phép gõ những thứ chung chung này, nghĩa là).
Qqwy

1
Tôi thấy Classes rất khó hiểu, thành thật mà nói, chúng chỉ là một bản thiết kế cho một cái gì đó cụ thể / nhưng chúng cũng có thể có các phương thức (công cụ tĩnh) và có thể hành xử như các đối tượng. Class có thực hiện giao diện hoặc thể hiện mà nó tạo không?
soundyogi

1
Vâng, họ có thể gây nhầm lẫn. Nhưng: Các lớp thực hiện các giao diện (chúng 'điền vào' các khoảng trống được đưa ra trong các phương thức giao diện. Nói cách khác: Chúng biến các hướng dẫn trừu tượng của giao diện theo một hướng dẫn cụ thể có thể ngay lập tức (tha thứ cho trò chơi chữ). Đối với 'các lớp hoạt động giống như các đối tượng': Trong các ngôn ngữ OOP thực sự như Ruby, Các lớp là các thể hiện của Lớp 'Lớp'. Đó là rùa tất cả các cách xuống.
Qqwy

ArrayKiểu xây dựng định nghĩa một functor duy nhất. Các thể hiện của nó cũng được gọi là "mảng" nhưng chúng không phải là functor. Mô tả ở đây nên được thực hiện chính xác hơn.
Dmitri Zaitsev

@DmitriZaitsev Bạn có thể giải thích? Vì vậy, những gì bạn đang nói là các trường hợp không phải là functor? Tôi không thấy sự xuất hiện trong đó vì bạn có một functor mới bằng cách ánh xạ qua một.
soundyogi

-4

Trong thực tế, functor có nghĩa là một đối tượng thực hiện toán tử cuộc gọi trong C ++. Trong ocaml tôi nghĩ functor đề cập đến một cái gì đó lấy một mô-đun làm đầu vào và đầu ra một mô-đun khác.


-6

Nói một cách đơn giản, một functor hoặc đối tượng hàm là một đối tượng lớp có thể được gọi giống như một hàm.

Trong C ++:

Đây là cách bạn viết một hàm

void foo()
{
    cout << "Hello, world! I'm a function!";
}

Đây là cách bạn viết một functor

class FunctorClass
{
    public:
    void operator ()
    {
        cout << "Hello, world! I'm a functor!";
    }
};

Bây giờ bạn có thể làm điều này:

foo(); //result: Hello, World! I'm a function!

FunctorClass bar;
bar(); //result: Hello, World! I'm a functor!

Điều làm cho những điều này trở nên tuyệt vời là bạn có thể giữ trạng thái trong lớp - hãy tưởng tượng nếu bạn muốn hỏi một hàm bao nhiêu lần nó được gọi. Không có cách nào để làm điều này một cách gọn gàng, gói gọn. Với một đối tượng hàm, nó cũng giống như bất kỳ lớp nào khác: bạn có một số biến đối tượng mà bạn tăng lên operator ()và một số phương thức để kiểm tra biến đó và mọi thứ đều gọn gàng như bạn muốn.


12
Không, những chức năng đó không phải là khái niệm lý thuyết loại được sử dụng bởi các ngôn ngữ FP.
Tobu

1
Tôi có thể thấy làm thế nào người ta có thể chứng minh rằng FunctorClasshoàn thành Luật Functor đầu tiên, nhưng bạn có thể phác thảo một bằng chứng cho Luật thứ hai không? Tôi không thấy nó đâu.
Jörg W Mittag

3
Bah, các bạn nói đúng. Tôi đã cố gắng giải quyết "web đã cung cấp các mô tả cực kỳ kỹ thuật" và overshot, cố gắng tránh, "Trong họ ngôn ngữ ML, functor là một mô-đun lấy một hoặc nhiều mô-đun khác làm tham số." Câu trả lời này, tuy nhiên, là tốt, xấu. Đơn giản hóa và thiếu xác định. Tôi bị cám dỗ để đánh bại nó, nhưng tôi sẽ để nó cho các thế hệ tương lai lắc đầu tại :)
Matt

Tôi rất vui vì bạn đã để lại câu trả lời và bình luận, vì nó giúp đóng khung vấn đề. Cảm ơn bạn! Tôi gặp rắc rối ở chỗ hầu hết các câu trả lời được viết bằng chữ Haskell hoặc OCaml, và với tôi điều đó giống như giải thích cá sấu về cá sấu.
Cướp

-10

Functor không liên quan cụ thể đến lập trình chức năng. Nó chỉ là một "con trỏ" cho một chức năng hoặc một loại đối tượng nào đó, có thể được gọi là nó sẽ là một chức năng.


8
Có một khái niệm FP cụ thể của một functor (từ lý thuyết danh mục), nhưng bạn đúng rằng cùng một từ cũng được sử dụng cho những thứ khác trong các ngôn ngữ không phải là FP.
Craig Stuntz

Bạn có chắc chắn rằng con trỏ hàm là Functor? Tôi không thấy cách con trỏ hàm thực hiện hai Luật Functor, đặc biệt là Luật Functor thứ hai (bảo toàn thành phần hình thái). Bạn có một bằng chứng cho điều đó? (Chỉ là một bản phác thảo thô.)
Jörg W Mittag
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.