Haskell: <*> được phát âm như thế nào? [đóng cửa]


109

Làm thế nào để bạn phát âm các chức năng này trong typeclass Ứng dụng:

(<*>) :: f (a -> b) -> f a -> f b
(*>)  :: f a -> f b -> f b
(<*)  :: f a -> f b -> f a

(Đó là, nếu họ không phải là toán tử, họ có thể được gọi là gì?)

Lưu ý thêm, nếu bạn có thể đổi tên purethành một cái gì đó thân thiện hơn với những người không phải là nhà toán học, bạn sẽ gọi nó là gì?


6
@J Cooper ... bạn có thể nghe cách chúng tôi phát âm nó không? :) Bạn có thể muốn đăng yêu cầu trên meta.stackoverflow.com về tính năng ghi âm và phát lại giọng nói :).
Kiril

8
Nó được phát âm là "Rất tiếc, họ đã thực sự hết người vận hành, phải không?" Ngoài ra, một cái tên hay cho purecó thể là makeApplicative.
Chuck

@Lirik, Vâng, tôi đoán bởi Phát âm tôi có nghĩa là "whaddya gọi điều này" :) @Chuck, gửi bạn puređề nghị như một câu trả lời và tôi sẽ upvote bạn
J Cooper

6
(<*>) là phiên bản Control.Applicative của "ap" của Control.Monad, vì vậy "ap" có lẽ là tên thích hợp nhất.
Edward KMETT

11
Tôi sẽ gọi nó là một chiếc xe đạp, nhưng đó chỉ là tôi.
RCIX

Câu trả lời:


245

Xin lỗi, tôi thực sự không biết toán học của mình, vì vậy tôi tò mò muốn biết cách phát âm các hàm trong kiểu chữ Ứng dụng

Tôi nghĩ rằng việc biết toán của bạn hay không, phần lớn không liên quan. Như bạn có thể đã biết, Haskell vay mượn một vài thuật ngữ từ các lĩnh vực toán học trừu tượng khác nhau, đáng chú ý nhất là Lý thuyết phạm trù , từ đó chúng ta có được các hàm và đơn nguyên. Việc sử dụng các thuật ngữ này trong Haskell hơi khác với các định nghĩa toán học chính thức, nhưng chúng thường đủ gần để trở thành các thuật ngữ mô tả tốt.

Lớp Applicativekiểu nằm ở đâu đó giữa FunctorMonad, vì vậy người ta sẽ mong đợi nó có cơ sở toán học tương tự. Tài liệu cho Control.Applicativemô-đun bắt đầu bằng:

Mô-đun này mô tả một cấu trúc trung gian giữa một bộ chức năng và một đơn nguyên: nó cung cấp các biểu thức và giải trình tự thuần túy, nhưng không có ràng buộc. (Về mặt kỹ thuật, một đầu dò monoidal lỏng lẻo mạnh mẽ.)

Hừ!

class (Functor f) => StrongLaxMonoidalFunctor f where
    . . .

MonadTôi nghĩ không hoàn toàn hấp dẫn như vậy.

Về cơ bản, tất cả những điều này tóm lại là Applicativekhông tương ứng với bất kỳ khái niệm nào đặc biệt thú vị về mặt toán học, vì vậy không có thuật ngữ sẵn sàng nào xoay quanh cách nó được sử dụng trong Haskell. Vì vậy, hãy đặt toán học sang một bên ngay bây giờ.


Nếu chúng ta muốn biết (<*>)nó được gọi là gì, nó có thể hữu ích để biết nó về cơ bản nghĩa là gì.

Vì vậy, có chuyện gì thế với Applicative, dù sao, và tại sao làm chúng ta gọi nó vậy?

Có gì Applicativesố tiền để trong thực tế là một cách để nâng tùy ý chức năng vào một Functor. Hãy xem xét sự kết hợp của Maybe(được cho là kiểu dữ liệu không tầm thường đơn giản nhất Functor) và Bool(tương tự như vậy là kiểu dữ liệu không tầm thường đơn giản nhất).

maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not

Chức năng này fmapcho phép chúng tôi nâng cấp nottừ làm việc Boolsang làm việc Maybe Bool. Nhưng nếu chúng ta muốn nâng (&&)thì sao?

maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)

Chà, đó không phải là điều chúng ta muốn chút nào ! Trên thực tế, nó khá vô dụng. Chúng ta có thể cố gắng khéo léo và lẻn Boolvào một người khác Maybetừ phía sau ...

maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)

... nhưng điều đó không tốt. Có điều, nó sai. Đối với một điều khác, nó xấu xí . Chúng tôi có thể tiếp tục thử, nhưng hóa ra là không có cách nào để nâng một hàm gồm nhiều đối số hoạt động trên một hàm tùy ýFunctor . Làm phiền!

Mặt khác, chúng ta có thể làm điều đó một cách dễ dàng nếu chúng ta sử dụng Maybe's Monaddụ:

maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
                  y' <- y
                  return (x' && y')

Bây giờ, đó là rất nhiều rắc rối chỉ để dịch một hàm đơn giản - đó là lý do tại sao Control.Monadcung cấp một hàm để thực hiện nó tự động , liftM2. 2 trong tên của nó đề cập đến thực tế là nó hoạt động trên các chức năng của chính xác hai đối số; các hàm tương tự tồn tại cho các hàm đối số 3, 4 và 5. Các hàm này tốt hơn , nhưng không hoàn hảo và việc chỉ định số lượng đối số là xấu và vụng về.

Điều này đưa chúng ta đến với bài báo đã giới thiệu lớp Loại ứng dụng . Trong đó, các tác giả đưa ra hai nhận xét về cơ bản:

  • Nâng các hàm đa đối số thành một Functorlà một việc rất tự nhiên phải làm
  • Làm như vậy không yêu cầu toàn bộ khả năng của Monad

Ứng dụng chức năng bình thường được viết bằng cách ghép nối các thuật ngữ đơn giản, vì vậy, để làm cho "ứng dụng được nâng cấp" trở nên đơn giản và tự nhiên nhất có thể, bài báo giới thiệu các toán tử infix để ứng dụng, được nâng lênFunctor và một lớp loại để cung cấp những gì cần thiết cho điều đó .

Tất cả những điều đó đưa chúng ta đến điểm sau: (<*>)chỉ đơn giản là đại diện cho ứng dụng hàm - vậy tại sao lại phát âm nó khác với việc bạn sử dụng "toán tử ghép nối" khoảng trắng?

Nhưng nếu điều đó không hài lòng lắm, chúng ta có thể thấy rằng Control.Monadmô-đun cũng cung cấp một chức năng thực hiện điều tương tự cho các monads:

ap :: (Monad m) => m (a -> b) -> m a -> m b

apTất nhiên, ở đâu là viết tắt của "apply". Vì bất kỳ cái nào Monadcũng có thể có Applicativeapchỉ cần tập hợp con của các tính năng có trong cái sau, chúng ta có thể nói rằng nếu (<*>)không phải là một toán tử, thì nó nên được gọi ap.


Chúng ta cũng có thể tiếp cận mọi thứ từ hướng khác. Các Functorhoạt động nâng được gọi là fmapbởi vì nó là một sự tổng quát của maphoạt động trên danh sách. Loại chức năng nào trên danh sách sẽ hoạt động như thế (<*>)nào? Tất nhiên, có những gì aptrong danh sách, nhưng bản thân nó không đặc biệt hữu ích.

Trên thực tế, có một cách giải thích có lẽ tự nhiên hơn cho các danh sách. Bạn nghĩ đến điều gì khi nhìn vào kiểu chữ ký sau đây?

listApply :: [a -> b] -> [a] -> [b]

Có điều gì đó rất hấp dẫn về ý tưởng xếp các danh sách song song, áp dụng từng chức năng trong phần tử đầu tiên cho phần tử tương ứng của phần tử thứ hai. Thật không may cho người bạn cũ của chúng tôi Monad, thao tác đơn giản này vi phạm luật đơn nguyên nếu danh sách có độ dài khác nhau. Nhưng nó có ích Applicative, trong trường hợp đó (<*>)trở thành một cách xâu chuỗi lại với nhau một phiên bản tổng quát của zipWith, vì vậy có lẽ chúng ta có thể hình dung cách gọi nó fzipWith?


Ý tưởng nén này thực sự mang lại cho chúng ta vòng tròn đầy đủ. Nhớ lại công cụ toán học trước đó, về các bộ chức năng đơn tử? Như tên cho thấy, đây là một cách kết hợp cấu trúc của monoids và functors, cả hai đều là các lớp kiểu Haskell quen thuộc:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

class Monoid a where
    mempty :: a
    mappend :: a -> a -> a

Những thứ này sẽ trông như thế nào nếu bạn đặt chúng vào một chiếc hộp và lắc nó lên một chút? Từ Functorchúng tôi sẽ giữ ý tưởng về một cấu trúc độc lập với tham số kiểu của nó và từ Monoidchúng tôi sẽ giữ nguyên dạng tổng thể của các hàm:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ?
    mfAppend :: f ? -> f ? -> f ?

Chúng tôi không muốn giả định rằng có một cách để tạo ra Functormột giá trị "trống" thực sự và chúng tôi không thể gợi ra một giá trị của một kiểu tùy ý, vì vậy chúng tôi sẽ sửa kiểu mfEmptyf ().

Chúng tôi cũng không muốn bắt buộc mfAppendphải cần một tham số kiểu nhất quán, vì vậy bây giờ chúng tôi có điều này:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f ?

Loại kết quả để làm mfAppendgì? Chúng tôi có hai loại tùy ý mà chúng tôi không biết gì về nó, vì vậy chúng tôi không có nhiều lựa chọn. Điều hợp lý nhất là chỉ cần giữ cả hai:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f (a, b)

Tại thời điểm mfAppendnày rõ ràng là một phiên bản tổng quát của zipdanh sách và chúng tôi có thể Applicativedễ dàng tạo lại :

mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)

Điều này cũng cho chúng ta thấy rằng purecó liên quan đến phần tử nhận dạng của a Monoid, vì vậy các tên hay khác cho nó có thể là bất kỳ thứ gì gợi ý giá trị đơn vị, phép toán null hoặc tương tự.


Điều đó dài dòng, vì vậy để tóm tắt:

  • (<*>) chỉ là một ứng dụng chức năng đã được sửa đổi, vì vậy bạn có thể đọc nó là "ap" hoặc "áp dụng", hoặc giải thích hoàn toàn theo cách bạn làm với ứng dụng chức năng bình thường.
  • (<*>)cũng khái quát zipWithvề danh sách, vì vậy bạn có thể đọc nó là "zip functors with", tương tự như đọc fmaplà "map a functor với".

Đầu tiên là gần với mục đích của Applicativelớp kiểu - như tên cho thấy - vì vậy đó là những gì tôi đề xuất.

Trên thực tế, tôi khuyến khích sử dụng tự do và không phát âm, của tất cả các toán tử ứng dụng đã nâng :

  • (<$>), nâng một hàm đối số đơn thành một Functor
  • (<*>), chuỗi này chuỗi một hàm đa đối số thông qua một Applicative
  • (=<<), liên kết một hàm nhập a Monadvào một phép tính hiện có

Về cơ bản, cả ba đều chỉ là ứng dụng chức năng thông thường, được gia vị thêm một chút.


6
@Colin Cochrane: Bạn có chắc là bạn đã không viết sai chính tả "dài dòng" ở đó không? :) Nhưng này, tôi sẽ lấy nó! Tôi luôn cảm thấy điều đó Applicativevà phong cách thành ngữ chức năng mà nó quảng bá không nhận được đủ sự yêu thích, vì vậy tôi không thể cưỡng lại cơ hội khai thác những phẩm chất của nó một chút như một phương tiện để giải thích cách tôi (không) phát âm (<*>).
CA McCann

+1! Xin vui lòng được dài dòng tương tự tại stackoverflow.com/questions/2104446/…
Greg Bacon,

6
Liệu Haskell đó có đường cú pháp cho Applicative's! Một cái gì đó như [| f a b c d |](theo đề xuất của bài báo gốc). Sau đó chúng ta sẽ không cần <*>combinator và bạn sẽ tham khảo một biểu thức như một ví dụ về "Ứng dụng chức năng trong một bối cảnh functorial"
Tom Crockett

1
@FredOverflow: Không, ý tôi là Monad. Hoặc Functorhoặc Monoidhoặc bất kỳ thứ gì khác có một thuật ngữ được thiết lập tốt bao gồm ít hơn ba tính từ. "Ứng dụng" chỉ đơn thuần là một cái tên hấp dẫn, mặc dù có tính mô tả hợp lý, gắn vào một cái gì đó mà đúng hơn là cần một cái tên.
CA McCann

1
@pelotom: xem [ stackoverflow.com/questions/12014524/… nơi những người tử tế đã chỉ cho tôi hai cách để có được gần như ký hiệu đó.
AndrewC

21

Vì tôi không có tham vọng cải thiện câu trả lời kỹ thuật của CA McCann , nên tôi sẽ giải quyết câu hỏi mượt mà hơn:

Nếu bạn có thể đổi tên purethành một cái gì đó thân thiện hơn với podunks như tôi, bạn sẽ gọi nó là gì?

Để thay thế, đặc biệt vì không có hồi kết đối với Monadphiên bản đầy tức giận và phản bội liên tục chống lại phiên bản, được gọi là " return", tôi đề xuất một cái tên khác, gợi ý chức năng của nó theo cách có thể đáp ứng yêu cầu nhất của các lập trình viên mệnh lệnh , và các chức năng nhất ... tốt, hy vọng, mọi người đều có thể khiếu nại như vậy về: inject.

Nhận một giá trị. "Tiêm" nó vào Functor, Applicative, Monad, hoặc những gì-có-bạn. Tôi bỏ phiếu cho " inject" và tôi đã chấp thuận tin nhắn này.


4
Tôi thường nghiêng về một cái gì đó như "đơn vị" hoặc "nâng", nhưng chúng đã có quá nhiều nghĩa khác trong Haskell. injectlà một cái tên xuất sắc và có lẽ tốt hơn của tôi, mặc dù như một lưu ý nhỏ, "tiêm" được sử dụng trong - tôi nghĩ - Smalltalk và Ruby cho một phương pháp gấp trái của một số loại. Tôi không bao giờ hiểu rằng lựa chọn tên, mặc dù ...
CA McCann

3
Đây là một luồng rất cũ, nhưng tôi nghĩ rằng injecttrong Ruby & Smalltalk được sử dụng vì nó giống như bạn đang "tiêm" một toán tử vào giữa mỗi phần tử trong danh sách. Ít nhất, đó là cách tôi luôn nghĩ về nó.
Jonathan Sterling

1
Để lấy lại chuỗi bên cũ đó: Bạn không tiêm toán tử, bạn đang thay thế (loại bỏ) các hàm tạo đã có ở đó. (Xem ngược lại, bạn đang đưa dữ liệu cũ vào một kiểu mới.) Đối với danh sách, việc loại bỏ chỉ là foldr. (Bạn thay thế (:)[], trong đó (:)lấy 2 args và []là một hằng số, do đó foldr (+) 0 (1:2:3:[])1+2+3+0.) Về Boolnó chỉ if- then-else (hai hằng số, chọn một) và vì Maybenó được gọi là maybe… Haskell không có tên / hàm duy nhất cho điều này, vì tất cả đều có các kiểu khác nhau (nói chung elim chỉ là đệ quy / cảm ứng)
không ai

@CAMcCann Smalltalk lấy tên đó từ một bài hát của Arlo Guthrie nói về dự thảo cho chiến tranh Việt Nam, trong đó những người đàn ông trẻ tuổi bất hạnh được thu thập, lựa chọn, đôi khi bị từ chối, và nếu không thì bị tiêm nhiễm.
Tom Anderson

7

Tóm lại:

  • <*>bạn có thể gọi nó là áp dụng . Vì vậy, Maybe f <*> Maybe acó thể được phát âm là apply Maybe foverMaybe a .

  • Bạn có thể đổi tên purethành of, giống như nhiều thư viện JavaScript làm. Trong JS, bạn có thể tạo Maybevới Maybe.of(a).

Ngoài ra, wiki của Haskell có một trang về cách phát âm của các toán tử ngôn ngữ tại đây


3
(<*>) -- Tie Fighter
(*>)  -- Right Tie
(<*)  -- Left Tie
pure  -- also called "return"

Nguồn: Lập trình Haskell từ Những Nguyên tắc Đầu tiên , của Chris Allen và Julie Moronuki


Những cái tên đó vẫn chưa chính xác, theo như tôi có thể nói.
dfeuer

@dfeuer hãy đợi thế hệ Haskeller tiếp theo đang sử dụng cuốn sách đó làm tài liệu học tập chính của họ.
dmvianna,

1
Nó có thể xảy ra. Tuy nhiên, những cái tên thật tệ hại, vì chúng không liên quan đến ý nghĩa.
dfeuer

1
@dfeuer, tôi không thấy giải pháp tốt ở đâu cả. "ap" / "apply" cũng mơ hồ như "tie Fight". Mọi thứ đều là ứng dụng chức năng. Tuy nhiên, một cái tên có màu xanh dương có thể có được ý nghĩa thông qua việc sử dụng. "Apple" là một ví dụ điển hình. Nhân tiện, trả về đơn nguyên ứng dụng thuần túy. Không có phát minh nào ở đây.
dmvianna,
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.