Tôi không hiểu "nâng" là gì. Trước tiên tôi có nên hiểu các đơn nguyên trước khi hiểu "thang máy" là gì không? (Tôi hoàn toàn không biết gì về các đơn vị, quá :) Hoặc ai đó có thể giải thích cho tôi bằng những từ đơn giản?
Tôi không hiểu "nâng" là gì. Trước tiên tôi có nên hiểu các đơn nguyên trước khi hiểu "thang máy" là gì không? (Tôi hoàn toàn không biết gì về các đơn vị, quá :) Hoặc ai đó có thể giải thích cho tôi bằng những từ đơn giản?
Câu trả lời:
Nâng là một mô hình thiết kế hơn là một khái niệm toán học (mặc dù tôi hy vọng ai đó ở đây sẽ bác bỏ tôi bằng cách chỉ ra cách thang máy là một danh mục hoặc một cái gì đó).
Thông thường bạn có một số loại dữ liệu với một tham số. Cái gì đó như
data Foo a = Foo { ...stuff here ...}
Giả sử bạn thấy rằng có rất nhiều cách sử dụng Foo
lấy các kiểu số ( Int
, Double
v.v.) và bạn tiếp tục phải viết mã mở khóa các số này, thêm hoặc nhân chúng, sau đó bọc chúng lại. Bạn có thể rút ngắn điều này bằng cách viết mã unrap-and-quấn một lần. Theo truyền thống, chức năng này được gọi là "thang máy" vì nó trông như thế này:
liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c
Nói cách khác, bạn có một hàm lấy hàm hai đối số (chẳng hạn như (+)
toán tử) và biến nó thành hàm tương đương cho Foos.
Vì vậy, bây giờ bạn có thể viết
addFoo = liftFoo2 (+)
Chỉnh sửa: thêm thông tin
Tất nhiên bạn có thể có liftFoo3
, liftFoo4
và như vậy. Tuy nhiên điều này thường không cần thiết.
Bắt đầu với việc quan sát
liftFoo1 :: (a -> b) -> Foo a -> Foo b
Nhưng đó là chính xác như fmap
. Vì vậy, thay vì liftFoo1
bạn sẽ viết
instance Functor Foo where
fmap f foo = ...
Nếu bạn thực sự muốn hoàn toàn đều đặn, bạn có thể nói
liftFoo1 = fmap
Nếu bạn có thể biến Foo
thành một functor, có lẽ bạn có thể biến nó thành một functor ứng dụng. Trong thực tế, nếu bạn có thể viết liftFoo2
thì ví dụ ứng dụng trông như thế này:
import Control.Applicative
instance Applicative Foo where
pure x = Foo $ ... -- Wrap 'x' inside a Foo.
(<*>) = liftFoo2 ($)
Các (<*>)
nhà điều hành cho Foo có kiểu
(<*>) :: Foo (a -> b) -> Foo a -> Foo b
Nó áp dụng hàm bọc cho giá trị được bọc. Vì vậy, nếu bạn có thể thực hiện liftFoo2
thì bạn có thể viết điều này về mặt nó. Hoặc bạn có thể thực hiện trực tiếp và không bận tâm liftFoo2
vì Control.Applicative
mô-đun bao gồm
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
và tương tự như vậy có liftA
và liftA3
. Nhưng bạn không thực sự sử dụng chúng thường xuyên vì có một nhà điều hành khác
(<$>) = fmap
Điều này cho phép bạn viết:
result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4
Thuật ngữ myFunction <$> arg1
trả về một hàm mới được bọc trong Foo. Điều này lần lượt có thể được áp dụng cho đối số tiếp theo bằng cách sử dụng (<*>)
, v.v. Vì vậy, bây giờ thay vì có một chức năng nâng cho mọi arity, bạn chỉ cần có một chuỗi ứng dụng daisy.
lift id == id
và lift (f . g) == (lift f) . (lift g)
.
id
và .
là thành phần mũi tên và mũi tên tương ứng của một số loại, tương ứng. Thông thường khi nói về Haskell, danh mục được đề cập là "Hask", có mũi tên là các chức năng Haskell (nói cách khác, id
và .
đề cập đến các chức năng Haskell mà bạn biết và yêu thích).
instance Functor Foo
, không instance Foo Functor
, phải không? Tôi sẽ tự chỉnh sửa nhưng tôi không chắc chắn 100%.
Paul's và yairchu đều là những lời giải thích tốt.
Tôi muốn thêm rằng hàm được nâng lên có thể có số lượng đối số tùy ý và chúng không phải cùng loại. Ví dụ: bạn cũng có thể định nghĩa thang máyFoo1:
liftFoo1 :: (a -> b) -> Foo a -> Foo b
Nói chung, việc nâng các hàm lấy 1 đối số được ghi lại trong lớp loại Functor
và hoạt động nâng được gọi là fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
Lưu ý sự tương đồng với liftFoo1
loại của. Trong thực tế, nếu bạn có liftFoo1
, bạn có thể tạo Foo
một ví dụ về Functor
:
instance Functor Foo where
fmap = liftFoo1
Hơn nữa, việc khái quát hóa việc nâng lên một số lượng đối số tùy ý được gọi là kiểu áp dụng . Đừng bận tâm đến việc này cho đến khi bạn nắm bắt được việc nâng các hàm với một số lượng đối số cố định. Nhưng khi bạn làm, Tìm hiểu cho bạn một Haskell có một chương tốt về điều này. Các Typeclassopedia là một tài liệu tốt mô tả functor và applicative (cũng như các lớp học kiểu khác cuộn xuống để chương ngay trong tài liệu đó).
Hi vọng điêu nay co ich!
Hãy bắt đầu với một ví dụ (một số khoảng trắng được thêm vào để trình bày rõ ràng hơn):
> import Control.Applicative
> replicate 3 'a'
"aaa"
> :t replicate
replicate :: Int -> b -> [b]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f b -> f [b]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> ['a','b','c']
"abc"
liftA2
biến đổi một hàm của các kiểu đơn giản thành một hàm có cùng kiểu được gói trong mộtApplicative
, chẳng hạn như danh sách IO
, v.v.
Một thang máy phổ biến là lift
từ Control.Monad.Trans
. Nó biến đổi một hành động đơn nguyên của một đơn nguyên thành một hành động của một đơn nguyên được chuyển đổi.
Nói chung, "nâng" nâng một chức năng / hành động thành một loại "được bọc" (vì vậy chức năng ban đầu sẽ hoạt động "dưới các kết thúc tốt đẹp").
Cách tốt nhất để hiểu điều này, và các đơn nguyên, v.v., và để hiểu tại sao chúng hữu ích, có lẽ là viết mã và sử dụng nó. Nếu có bất cứ điều gì bạn đã mã hóa trước đây mà bạn nghi ngờ có thể được hưởng lợi từ điều này (nghĩa là điều này sẽ làm cho mã đó ngắn hơn, v.v.), chỉ cần dùng thử và bạn sẽ dễ dàng nắm bắt được khái niệm này.
Nâng là một khái niệm cho phép bạn chuyển đổi một chức năng thành một chức năng tương ứng trong một cài đặt khác (thường là tổng quát hơn)
Theo hướng dẫn sáng bóng này , functor là một số container (như Maybe<a>
, List<a>
hoặc Tree<a>
có thể lưu trữ các phần tử của một số loại khác, a
). Tôi đã sử dụng ký hiệu chung chung Java <a>
, cho loại phần tử a
và nghĩ về các phần tử như quả mọng trên cây Tree<a>
. Có một hàm fmap
, trong đó có một hàm chuyển đổi thành phần a->b
và vùng chứa functor<a>
. Nó áp dụng a->b
cho mọi yếu tố của container có hiệu quả chuyển đổi nó thành functor<b>
. Khi chỉ có đối số đầu tiên được cung cấp, a->b
hãy fmap
đợi functor<a>
. Nghĩa là, việc cung cấp a->b
một mình biến hàm cấp độ phần tử này thành hàm functor<a> -> functor<b>
hoạt động trên các container. Điều này được gọi là nângcủa hàm. Bởi vì container cũng được gọi là functor , Functor chứ không phải Monads là điều kiện tiên quyết để nâng. Monads là loại "song song" để nâng. Cả hai đều dựa vào khái niệm Functor và làm f<a> -> f<b>
. Sự khác biệt là nâng sử dụng a->b
cho chuyển đổi trong khi Monad yêu cầu người dùng xác định a -> f<b>
.
r
đến một loại (hãy sử dụng c
cho nhiều loại), là Functor. Chúng không "chứa" bất kỳ c
. Trong trường hợp này, fmap là thành phần chức năng, lấy một a -> b
chức năng và một chức năng r -> a
, để cung cấp cho bạn một r -> b
chức năng mới. Vẫn không có container. Ngoài ra, nếu tôi có thể, tôi sẽ đánh dấu lại cho câu cuối cùng.
fmap
là một chức năng, và không "chờ đợi" bất cứ điều gì; "Container" là một Functor là toàn bộ điểm nâng. Ngoài ra, Monads, nếu có, là một ý tưởng kép để nâng: Monad cho phép bạn sử dụng thứ gì đó đã được nâng lên một số lần tích cực, như thể nó chỉ được nâng lên một lần - điều này được gọi là làm phẳng .
To wait
, to expect
, to anticipate
là những từ đồng nghĩa. Bằng cách nói "chức năng chờ" tôi có nghĩa là "chức năng dự đoán".
b = 5 : a
và f 0 = 55
f n = g n
, cả hai đều liên quan đến giả đột biến "container". Ngoài ra, thực tế là các danh sách thường được lưu trữ hoàn toàn trong bộ nhớ trong khi các chức năng thường được lưu trữ dưới dạng tính toán. Nhưng các danh sách ghi nhớ / đơn hình không được lưu trữ giữa các cuộc gọi, cả hai đều phá vỡ ý tưởng đó.