Nâng hạ thế giới là gì trong Haskell?


138

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?


9
Có thể hữu ích, có thể không: haskell.org/haskellwiki/Lifting
kennytm

Câu trả lời:


179

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 Foolấy các kiểu số ( Int, Doublev.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, liftFoo4và 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ì liftFoo1bạ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 Foothà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 liftFoo2thì 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 liftFoo2thì 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 liftFoo2Control.Applicativemô-đun bao gồm

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

và tương tự như vậy có liftAliftA3. 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 <$> arg1trả 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.


26
Có lẽ đáng để nhắc nhở rằng thang máy nên tôn trọng luật tiêu chuẩn lift id == idlift (f . g) == (lift f) . (lift g).
Carlos Scheidegger

13
Thang máy thực sự là "một thể loại hoặc một cái gì đó". Carlos vừa liệt kê các luật Functor, trong đó id.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.đề cập đến các chức năng Haskell mà bạn biết và yêu thích).
Dan Burton

3
Điều này nên đọc 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%.
amalloy

2
Nâng mà không có ứng dụng là = Functor. Ý tôi là bạn có 2 sự lựa chọn: Functor hoặc Applicative Functor. Các đầu tiên nâng chức năng tham số duy nhất các chức năng đa tham số thứ hai. Khá nhiều đó là nó. Đúng? Đó không phải là khoa học tên lửa :) nó chỉ giống như nó. Cảm ơn câu trả lời tuyệt vời btw!
jhegedus

2
@atc: đây là một phần ứng dụng. Xem wiki.haskell.org/Partial_application
Paul Johnson

41

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 Functorvà 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 liftFoo1loại của. Trong thực tế, nếu bạn có liftFoo1, bạn có thể tạo Foomộ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ả functorapplicative (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!


25

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"

liftA2biế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à lifttừ 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.


13

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)

hãy xem http://haskell.org/haskellwiki/Lifting


40
Vâng, nhưng trang đó bắt đầu "Chúng tôi thường bắt đầu với một functor (covariant) ...". Không chính xác người mới thân thiện.
Paul Johnson

3
Nhưng "functor" được liên kết, vì vậy người mới có thể chỉ cần nhấp vào đó để xem Functor là gì. Phải thừa nhận rằng trang liên kết không tốt lắm. Tôi cần phải có một tài khoản và sửa nó.
jrockway

10
Đó là một vấn đề tôi đã thấy trên các trang web lập trình chức năng khác; mỗi khái niệm được giải thích theo các khái niệm khác (không quen thuộc) cho đến khi người mới đi vòng tròn đầy đủ (và làm tròn khúc quanh). Phải là một cái gì đó để làm với thích đệ quy.
DNA

2
Bình chọn cho liên kết này. Nâng làm cho kết nối giữa một thế giới và một thế giới khác.
eccstartup

3
Câu trả lời như thế này chỉ tốt khi bạn đã hiểu chủ đề.
doubleOrt

-2

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ử avà 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->bvà vùng chứa functor<a>. Nó áp dụng a->bcho 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->bhãy fmapđợi functor<a>. Nghĩa là, việc cung cấp a->bmộ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->bcho chuyển đổi trong khi Monad yêu cầu người dùng xác định a -> f<b>.


5
Tôi đã cho bạn một dấu ấn, bởi vì "một functor là một số container" là mồi lửa có hương vị troll. Ví dụ: các hàm từ một số rđến một loại (hãy sử dụng ccho 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 -> bchức năng và một chức năng r -> a, để cung cấp cho bạn một r -> bchứ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.
BMeph

1
Ngoài ra, fmaplà 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 .
BMeph

1
@BMeph To wait, to expect, to anticipatelà 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".
Val

@BMeph Tôi sẽ nói rằng thay vì nghĩ về một hàm như một ví dụ cho ý tưởng rằng hàm functor là các thùng chứa, bạn nên nghĩ về thể hiện functor của hàm như là một ví dụ cho ý tưởng rằng các hàm không chứa. Hàm là ánh xạ từ một miền sang một tên miền, miền là sản phẩm chéo của tất cả các tham số, tên miền là loại đầu ra của hàm. Theo cách tương tự, một danh sách là ánh xạ từ Naturals sang loại bên trong của danh sách (tên miền -> tên miền). Chúng thậm chí trở nên giống nhau hơn nếu bạn ghi nhớ chức năng hoặc không giữ danh sách.
dấu chấm phẩy

@BMeph, một trong những danh sách lý do duy nhất được cho là giống như một container hơn là trong nhiều ngôn ngữ, chúng có thể bị đột biến, trong khi các chức năng truyền thống thì không thể. Nhưng trong Haskell, ngay cả đó không phải là một tuyên bố công bằng vì không thể bị đột biến và cả hai đều có thể bị đột biến sao chép: b = 5 : af 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 đó.
dấu chấm phẩ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.