Có thể có chức năng currying và matrixdic cùng một lúc?


13

Tôi đang suy nghĩ về việc làm cho các chức năng currying và matrixdic có sẵn trong một ngôn ngữ lập trình chức năng được gõ động, nhưng tôi tự hỏi liệu nó có thể hay không.

Dưới đây là một số mã giả:

sum = if @args.empty then 0 else @args.head + sum @args.tail

được cho là để tổng hợp tất cả các đối số của nó. Sau đó, nếu sumchính nó được điều trị một số, thì kết quả là 0. ví dụ,

sum + 1

bằng 1, giả sử rằng +chỉ có thể làm việc trên các số. Tuy nhiên, thậm chí sum == 0là đúng, sumvẫn sẽ duy trì giá trị và thuộc tính chức năng của nó cho dù có bao nhiêu đối số được đưa ra (do đó "được áp dụng một phần" và "matrixdic" cùng một lúc), ví dụ, nếu tôi tuyên bố

g = sum 1 2 3

sau đó gbằng 6, tuy nhiên, chúng ta vẫn có thể áp dụng thêm g. Ví dụ, g 4 5 == 15là đúng. Trong trường hợp này, chúng ta không thể thay thế đối tượng gbằng một chữ 6, bởi vì mặc dù chúng mang lại cùng một giá trị khi được coi là một số nguyên, chúng chứa các mã khác nhau bên trong.

Nếu thiết kế này được sử dụng trong một ngôn ngữ lập trình thực sự, nó sẽ gây ra bất kỳ sự nhầm lẫn hoặc mơ hồ nào?


1
Nói một cách chính xác, sử dụng currying như một nguyên tắc cơ bản của ngôn ngữ có nghĩa là tất cả các chức năng đều là đơn nhất - không chỉ không có chức năng biến đổi, thậm chí không có bất kỳ chức năng nhị phân nào! Tuy nhiên, các chương trình trong ngôn ngữ đó sẽ vẫn trông như thể chúng có nhiều đối số và điều đó đúng với các hàm biến đổi giống như đối với các hàm thông thường.
Kilian Foth

Sau đó, câu hỏi của tôi được đơn giản hóa thành "một đối tượng có thể là một hàm và một giá trị phi hàm cùng một lúc không?" Trong ví dụ trên, sum0không có một cuộc tranh cãi và đệ quy gọi chính nó với một cuộc tranh cãi.
Michael Tsang

đó không phải là công việc của reduce?
quái vật ratchet

1
Hãy nhìn vào các chức năng bạn đang sử dụng trên args: empty, head, và tail. Đó là tất cả các chức năng của danh sách, cho thấy rằng có lẽ điều dễ dàng và đơn giản hơn sẽ làm là sử dụng một danh sách trong đó các công cụ biến đổi sẽ là. (Vì vậy, sum [1, 2, 3]thay vì sum 1 2 3)
Michael Shaw

Câu trả lời:


6

Làm thế nào varargs có thể được thực hiện? Chúng ta cần một số cơ chế để báo hiệu kết thúc danh sách đối số. Điều này có thể là

  • một giá trị terminator đặc biệt, hoặc
  • độ dài của danh sách vararg được truyền dưới dạng tham số phụ.

Cả hai cơ chế này có thể được sử dụng trong bối cảnh currying để thực hiện các varargs, nhưng việc gõ đúng trở thành một vấn đề lớn. Giả sử rằng chúng ta đang xử lý một hàm sum: ...int -> int, ngoại trừ hàm này sử dụng currying (vì vậy chúng ta thực sự có một kiểu giống hơn sum: int -> ... -> int -> int, ngoại trừ việc chúng ta không biết số lượng đối số).

Trường hợp: giá trị terminator: Hãy endlà terminator đặc biệt, và Tlà loại sum. Bây giờ chúng ta biết rằng áp dụng cho endhàm trả về : sum: end -> int, và áp dụng cho int chúng ta có một hàm giống như tổng khác : sum: int -> T. Do đó, Tsự kết hợp của các loại này : T = (end -> int) | (int -> T). Bằng cách thay thế T, chúng tôi nhận nhiều loại hình thể như end -> int, int -> end -> int, int -> int -> end -> int, vv Tuy nhiên, hầu hết các hệ thống loại không chứa các loại như vậy.

Trường hợp: độ dài rõ ràng: Đối số đầu tiên cho hàm vararg là số lượng varargs. Vì vậy sum 0 : int, sum 1 : int -> int, sum 3 : int -> int -> int -> intvv Điều này được hỗ trợ trong một số hệ thống loại và là một ví dụ về gõ phụ thuộc . Trên thực tế, số lượng các đối số sẽ là một tham số kiểu và không phải là một tham số thường - nó sẽ không có ý nghĩa cho các arity của hàm phụ thuộc vào một giá trị thời gian chạy, s = ((sum (floor (rand 3))) 1) 2rõ ràng là bệnh-gõ: đánh giá lại này cho một trong hai s = ((sum 0) 1) 2 = (0 1) 2, s = ((sum 1) 1) 2 = 1 2hoặc s = ((sum 2) 1) 2 = 3.

Trong thực tế, không nên sử dụng bất kỳ kỹ thuật nào trong số này vì chúng dễ bị lỗi và không có loại (có ý nghĩa) trong các hệ thống loại phổ biến. Thay vào đó, chỉ cần vượt qua một danh sách các giá trị như một tham số : sum: [int] -> int.

Có, một đối tượng có thể xuất hiện dưới dạng cả hàm và giá trị, ví dụ: trong một hệ thống loại có các ép buộc. Hãy sumlà một SumObj, trong đó có hai sự ép buộc:

  • coerce: SumObj -> int -> SumObjcho phép sumđược sử dụng như một chức năng, và
  • coerce: SumObj -> int cho phép chúng tôi trích xuất kết quả.

Về mặt kỹ thuật, đây là một biến thể của trường hợp giá trị terminator ở trên, với T = SumObjcoercelà một trình bao bọc cho loại. Trong nhiều ngôn ngữ hướng đối tượng, điều này có thể thực hiện được với quá tải toán tử, ví dụ C ++:

#include <iostream>
using namespace std;

class sum {
  int value;
public:
  explicit sum() : sum(0) {}
  explicit sum(int x) : value(x) {}
  sum operator()(int x) const { return sum(value + x); }  // function call overload
  operator int() const { return value; } // integer cast overload
};

int main() {
  int zero = sum();
  cout << "zero sum as int: " << zero << '\n';
  int someSum = sum(1)(2)(4);
  cout << "some sum as int: " << someSum << '\n';
}

Câu trả lời tuyệt vời! Hạn chế với việc đóng gói varargs lên trong một danh sách là bạn mất ứng dụng một phần của cà ri. Tôi đã chơi với phiên bản Python của phương pháp terminator của bạn, sử dụng một đối số từ khóa ..., force=False)để buộc ứng dụng của hàm ban đầu.
ThomasH

Bạn có thể tạo hàm thứ tự cao hơn của riêng bạn, áp dụng một phần hàm có danh sách, như curryList : ([a] -> b) -> [a] -> [a] -> b, curryList f xs ys = f (xs ++ ys).
Jack

2

Bạn có thể muốn xem triển khai printf này trong Haskell , cùng với mô tả về cách thức hoạt động của nó . Có một liên kết trên trang sau với bài viết của Oleg Kiselyov về việc làm điều này, cũng đáng đọc. Trên thực tế, nếu bạn đang thiết kế một ngôn ngữ chức năng, trang web của Oleg có lẽ nên được đọc bắt buộc.

Theo tôi, những cách tiếp cận này là một chút hack, nhưng chúng cho thấy rằng nó có thể. Tuy nhiên, nếu ngôn ngữ của bạn có tính năng gõ phụ thuộc hoàn toàn, thì nó đơn giản hơn nhiều. Một hàm matrixdic để tính tổng các đối số nguyên của nó sau đó có thể trông giống như thế này:

type SumType = (t : union{Int,Null}) -> {SumType, if t is Int|
                                         Int,     if t is Null}
sum :: SumType
sum (v : Int) = v + sum
sum (v : Null) = 0

Một sự trừu tượng để xác định kiểu đệ quy mà không cần đặt cho nó một tên rõ ràng có thể làm cho việc viết các hàm như vậy dễ dàng hơn.

Chỉnh sửa: tất nhiên, tôi chỉ đọc lại câu hỏi và bạn nói một ngôn ngữ được gõ động , tại thời điểm đó rõ ràng cơ học loại không thực sự phù hợp, và do đó câu trả lời của @ amon có thể chứa mọi thứ bạn cần. Ồ, tôi sẽ để nó ở đây trong trường hợp có ai gặp phải điều này trong khi tự hỏi làm thế nào để sử dụng nó trong một ngôn ngữ tĩnh ...


0

Đây là một phiên bản để xử lý các hàm matrixdic trong Python3 sử dụng cách tiếp cận "terminator" của @amon, bằng cách tận dụng các đối số tùy chọn của Python:

def curry_vargs(g):
    actual_args = []
    def f(a, force=False):
        nonlocal actual_args
        actual_args.append(a)
        if force:
            res = g(*actual_args)
            actual_args = []
            return res
        else:
            return f
    return f

def g(*args): return sum(args)
f = curry_vargs(g)
f(1)(2)(3)(4,True) # => 10

Hàm trả về fthu thập các đối số được truyền cho nó trong các lệnh gọi liên tiếp trong một mảng được ràng buộc trong phạm vi bên ngoài. Chỉ khi forceđối số là đúng, hàm ban đầu được gọi với tất cả các đối số được thu thập cho đến nay.

Hãy cẩn thận khi thực hiện việc này là bạn luôn phải truyền đối số đầu tiên để fbạn không thể tạo "thunk", một hàm trong đó tất cả các đối số bị ràng buộc và chỉ có thể được gọi với danh sách đối số trống (nhưng tôi nghĩ rằng điều này phù hợp với việc thực hiện điển hình của cà ri).

Một cảnh báo khác là một khi bạn vượt qua một đối số sai (ví dụ như loại sai), bạn phải làm lại chức năng ban đầu. Không có cách nào khác để thiết lập lại mảng bên trong, điều này chỉ được thực hiện sau khi thực hiện thành công chức năng curried.

Tôi không biết nếu câu hỏi đơn giản hóa của bạn, "một đối tượng có thể là một hàm và một giá trị không phải là hàm cùng một lúc không?", Có thể được thực hiện trong Python, như một tham chiếu đến một hàm mà không có dấu ngoặc đơn cho đối tượng hàm bên trong . Tôi không biết nếu điều này có thể được uốn cong để trả về một giá trị tùy ý.

Có lẽ sẽ dễ dàng trong Lisp, vì các ký hiệu Lisp có thể có một giá trị và giá trị hàm cùng một lúc; giá trị hàm được chọn đơn giản khi biểu tượng xuất hiện ở vị trí hàm (là phần tử đầu tiên trong danh sách).

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.