Python cà ri vs một phần

Đó là quá trình lấy một hàm nhận nhiều đối số và biến nó thành một hàm nhận một đối số và trả về một hàm khác nhận ít đối số hơn. Ví dụ: hãy xem chức năng bên dưới

def add[a, b]:
    return a + b

Một phiên bản được chỉnh sửa của chức năng sẽ trông như thế này

def add[a]:
    def add_a[b]:
        return a + b
    return add_a

hoặc như thế này

add = lambda a: lambda b: a + b

Bây giờ

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
6 trả về một hàm, nhưng
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
7 trả về số nguyên
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
8. Bạn có thể lưu trữ chức năng trung gian dưới dạng
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
9 và gọi nó sau, như
add = lambda a: lambda b: a + b
0 và
add = lambda a: lambda b: a + b
1

Tại sao cà ri?

Đây là một cách mà lập trình viên chức năng lưu và chuyển trạng thái. Đây cũng là một cách tuyệt vời để giảm tính toán tốn kém, đặc biệt là khi các hàm được xử lý thuần túy. Đôi khi nó cũng có thể là một lựa chọn phong cách cho các chức năng cà ri. Đối với một người, tôi thích giao diện của

add = lambda a: lambda b: a + b
2 hơn nhiều so với
add = lambda a: lambda b: a + b
3. [Bên cạnh đó, tôi cực kỳ yêu thích
add = lambda a: lambda b: a + b
4 và cực kỳ ghét
add = lambda a: lambda b: a + b
5, iykyk. ] Tìm kiếm nhanh trên google sẽ cung cấp cho bạn một số ví dụ về điều này, vì vậy tôi sẽ không dành quá nhiều thời gian cho câu hỏi “tại sao”. Bài viết này thực sự là về “làm thế nào”. Nếu bạn đang tìm kiếm các ví dụ, bạn có thể xem bài đăng này mà tôi tìm thấy trên các trang web liên mạng

Tôi nên lưu ý ở đây rằng trong một ngôn ngữ mà các hàm là các đối tượng hạng nhất và hỗ trợ các bao đóng [các hàm được định nghĩa bên trong các hàm khác nắm bắt hoặc có quyền truy cập vào các biến cục bộ của hàm kèm theo], chẳng hạn như Python, Haskell, Scheme . , các chức năng được xử lý có thể mạnh mẽ như các phiên bản không được xử lý của chúng, vì có thể xử lý bất kỳ hàm không được xử lý nào và ngược lại. Sau đây là một bằng chứng khá đơn giản về điều này bằng quy nạp

Nếu một hàm $f_n$ nhận các đối số $n$ thì bạn có thể biến hàm đó thành một hàm $c_n$ nhận một đối số và trả về một hàm $c_{n-1}$ nhận các đối số $n-1$ và có . Bạn có thể tiếp tục làm điều này cho đến khi bạn đạt đến $c_1$, nhận một đối số và gọi $f_n$ trên các đối số $n$ nhận được bởi $c_{n}, c_{n-1}, \ldots, c_1$, do đó . Dưới đây là một ví dụ về điều đó

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
3

Và tất nhiên, nếu bạn có hàm $c_n$, thì bạn có thể nhận được $f_n$ từ hàm đó bằng cách gọi $c_n$ $n$ lần với các đối số được cung cấp cho $f_n$, như minh họa bên dưới

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
4

Điều này chứng tỏ rằng có một sự tương ứng một đối một giữa các hàm được sắp xếp và các hàm nhận đối số $n$ cho bất kỳ số tự nhiên dương nào $n$

Còn những hàm không có đối số thì sao? . Và các hàm không thuần túy có thể tạo ra các kỳ lân khó phân tích hơn nhiều nên tôi không nói về chúng ở đây

Cà ri mọi thứ trong Python

từ chối trách nhiệm. Làm điều này theo quyết định của riêng bạn và chỉ với các chức năng thuần túy

Mặc dù tôi cực kỳ thích xem

add = lambda a: lambda b: a + b
6, nhưng tôi cực kỳ ghét định nghĩa của nó. Người ta có thể đơn giản hóa nó để

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
6

nhưng điều đó cũng không có vẻ đặc biệt hấp dẫn. Chỉ có Haskell là một cách viết gọn gàng

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
7

Nhưng nó vẫn làm lu mờ bản chất thực sự của hàm

add = lambda a: lambda b: a + b
7, đó là nó cần năm đối số và cộng tất cả chúng lại với nhau, điều này được thể hiện ngắn gọn nhất trong định nghĩa

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
9

Nhưng chờ đã, bây giờ

add = lambda a: lambda b: a + b
7 không được cà ri. Mô-đun
add = lambda a: lambda b: a + b
9 của Python có một chức năng gọi là
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
30 cho phép bạn áp dụng một phần các chức năng, như sau

add = lambda a: lambda b: a + b
3

Mặc dù đây là một cách để lấy tương đương với việc cà ri ra khỏi một chức năng không được cà ri, nhưng theo ý kiến ​​​​của tôi thì nó hơi dài dòng. Điều gì sẽ xảy ra nếu chúng ta có thể thêm một trình trang trí vào định nghĩa của

add = lambda a: lambda b: a + b
7 để làm nổi bật nó?

add = lambda a: lambda b: a + b
5

Người trang trí cà ri

Bây giờ chúng ta cần đưa ra một số quyết định thiết kế. Python là một ngôn ngữ rất linh hoạt. Người dùng có thể gọi một hàm với các đối số vị trí hoặc từ khóa. Một số đối số là tùy chọn, vì vậy người dùng có thể không bao giờ cung cấp chúng. Ngoài ra, lý do lớn nhất tại sao currying hữu ích là nó cho phép lập trình viên lưu trạng thái khi cần, và đôi khi bạn chỉ muốn gọi một hàm Curried như

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
32 thay vì
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
33, vì sau tất cả, bạn đã xác định hàm Curried của mình là
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
34 với một bộ trang trí . Tôi muốn người trang trí này xử lý tất cả những thứ đó một cách trang nhã và nó thực sự không khó lắm

Tôi đã quyết định xác định một hàm

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
35 nhận vào một số nguyên
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
36 và trả về một trình trang trí xử lý một hàm nhận ít nhất
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
36 đối số và khi nhiều đối số đó đã được cung cấp, hàm bao bọc sẽ được gọi với tất cả các đối số vị trí và từ khóa. Ví dụ,
add = lambda a: lambda b: a + b
7 sẽ được định nghĩa như sau

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
0

Để xem thêm ví dụ về điều này, vui lòng xem doctests của

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
35 trong repo github với tất cả mã. Và vui lòng đọc toàn bộ chuỗi tài liệu

Đang lưu trạng thái và một phần ứng dụng

Hãy thử thực hiện

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
35. Chúng tôi muốn một hàm trả về một hàm trang trí, vì vậy nó sẽ giống như thế này

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
1

Nếu

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
41 được cung cấp ít nhất
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
36 đối số trong tổng số [đối số vị trí cộng với từ khóa] thì nó chỉ cần gọi
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
43, nếu không nó sẽ trả về một ứng dụng một phần. một phần ứng dụng là gì? . Nếu nó được cung cấp ít nhất _______136 đối số thì nó sẽ gọi _______143, nếu không nó sẽ trả về một phần tính toán khác. Chúng ta có thể thực hiện hành vi như vậy trong một lớp
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
46

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
2

Lưu ý rằng phương thức ma thuật

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
47 trong Python làm cho một đối tượng có thể gọi được, nghĩa là bây giờ chúng ta có thể gọi các đối tượng
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
46 như thể chúng là các hàm. Bây giờ chúng ta có thể triển khai
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
35 khá đơn giản

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
3

Chúng ta thực sự có thể đơn giản hóa mã này hơn một chút, vì một đối tượng

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
46 không có
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
61 và không có
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
62 hoạt động giống hệt như bất kỳ đối tượng
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
46 nào khác. Chúng ta có thể loại bỏ hoàn toàn hàm
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
41 và có được cách triển khai khá thanh lịch của
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
35

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
4

Điều này sẽ giúp bạn đánh giá cao những gì đang thực sự xảy ra khi chúng ta đang cà ri. chúng tôi đang lưu trữ trạng thái và truyền nó đi khắp nơi, và đó là lý do tại sao đôi khi nó chính xác như những gì bạn muốn

Một định nghĩa chức năng thuần túy

Nếu bạn xem kỹ đoạn mã trên, bạn sẽ nhận thấy rằng các đối tượng

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
46 không bao giờ bị thay đổi và cũng chỉ có hai phương thức được gọi trên các đối tượng đó.
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
67 trong khi khởi tạo chúng và
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
47 trong khi gọi chúng dưới dạng hàm [trong mã có lẽ sử dụng hàm curried]. Có thể loại bỏ hoàn toàn lớp
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
46 và tái tạo hành vi tương tự với hai hàm
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
70 và
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
71 bên trong
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
72

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
5

Lưu ý rằng

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
71 có cùng nội dung với
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
74 và mã soạn sẵn trong
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
67 đã hoàn toàn biến mất. Việc triển khai
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
35 này, được gọi là
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
77 trong repo github, hoàn toàn là chức năng. Tôi vẫn thích triển khai bằng cách sử dụng
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
46 hơn vì người ta sẽ phải viết ra các lệnh gọi hàm trên giấy để hiểu điều gì đang diễn ra trong quá trình triển khai này, trong khi các kiểu đầu tiên trong lần triển khai đầu tiên có ý nghĩa hơn nhiều và làm nổi bật bản chất thực sự của những gì . e. , chuyển qua các chức năng được áp dụng một phần. Thật vậy, các loại trong triển khai chức năng là một tổ hợp tồi tệ của
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
79 và chúng hoàn toàn không thông báo cho người đọc, vì vậy tôi đã loại bỏ chúng trong repo github, nhưng các loại trong lần triển khai đầu tiên kể một câu chuyện rất hay

Bất chấp những thiếu sót về phong cách của việc triển khai chức năng, tôi đưa nó vào đây để đưa ra một điểm rất quan trọng. có thể chuyển trạng thái không chỉ trong các đối tượng mà còn trong các chức năng [chính xác hơn là các bao đóng]. Trường hợp cụ thể, chúng ta có thể thay thế đối tượng

def add[a]:
    def add_a[b]:
        return a + b
    return add_a
46 bằng các hàm xác định nó, vì nó là một đối tượng bất biến [về mặt kỹ thuật, nó là bất biến, nhưng có thể làm cho nó trở nên bất biến trong Python bằng cách xóa phương thức
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
91 trong
def add[a]:
    def add_a[b]:
        return a + b
    return add_a
67, và . Điều này cho thấy rằng có lẽ chúng ta chỉ có thể triển khai lại các lớp bằng cách sử dụng các hàm. Đây sẽ là chủ đề của một bài đăng trên blog trong tương lai [tiếp theo?]

Sự kết luận

Mặc dù Python không mặc định các chức năng của nó [không giống như Haskell], nhưng có một cách rõ ràng, thanh lịch để triển khai chức năng tương tự. Điều này cũng gợi ý rằng các hàm có thể tự lưu và chuyển trạng thái và các lớp chỉ là đường cú pháp cho một số loại hàm nhất định. Cuối cùng, nó phản ánh thực tế rằng phép tính Lambda, một khung trong đó khai báo biến, định nghĩa hàm và ứng dụng là những hành động khả thi duy nhất, đủ để lập mô hình cho tất cả quá trình tính toán

Bạn có thể tìm thấy tất cả các mã trong bài đăng này tại đây. Tôi thực sự khuyên bạn nên đọc các tài liệu để hiểu các quyết định thiết kế mà tôi đã đưa ra và tất cả các tính năng mà trình trang trí cà ri cung cấp

Là cà ri giống như ứng dụng một phần?

Không cần nhiều đối số. Cà ri khác xa với ứng dụng một phần . Tuy nhiên, có thể sử dụng một phần ứng dụng để thực hiện currying. Tuy nhiên, không thể tính một hàm lấy một số đối số ngẫu nhiên [cho đến khi bạn chắc chắn về các đối số].

Phương pháp một phần trong Python là gì?

Hàm một phần là gì? . Bạn có thể nghĩ về một hàm bộ phận như một phần mở rộng của một hàm được chỉ định khác. a component of metaprogramming in Python, a concept that refers to a programmer writing code that manipulates code. You can think of a partial function as an extension of another specified function.

Python có cà ri không?

Python không có cú pháp rõ ràng cho hàm Currying ; .

Nhập một phần trong Python là gì?

Các hàm một phần cho phép chúng ta sửa một số đối số nhất định của hàm và tạo một hàm mới . Thí dụ. từ funcools nhập một phần.

Chủ Đề