Cà ri JavaScript: các ứng dụng thực tế là gì?


172

Tôi không nghĩ rằng tôi đã mò mẫm cà ri chưa. Tôi hiểu những gì nó làm, và làm thế nào để làm điều đó. Tôi chỉ không thể nghĩ đến một tình huống tôi sẽ sử dụng nó.

Bạn đang sử dụng currying trong JavaScript (hoặc các thư viện chính sử dụng nó ở đâu)? Thao tác DOM hoặc ví dụ phát triển ứng dụng chung hoan nghênh.

Một trong những câu trả lời đề cập đến hoạt hình. Các hàm như slideUp, fadeInlấy một phần tử làm đối số và thông thường là một hàm được trả về hàm thứ tự cao với hàm hoạt hình mặc định của chế độ hoạt động thành công. Tại sao điều đó tốt hơn là chỉ áp dụng chức năng cao hơn với một số mặc định?

Có bất kỳ nhược điểm để sử dụng nó?

Theo yêu cầu ở đây là một số tài nguyên tốt về cà ri JavaScript:

Tôi sẽ thêm nhiều hơn khi họ thu thập ý kiến.


Vì vậy, theo các câu trả lời, cà ri và ứng dụng một phần nói chung là các kỹ thuật tiện lợi.

Nếu bạn thường xuyên tinh chỉnh một chức năng cấp cao bằng cách gọi nó với cùng cấu hình, bạn có thể dùng cà ri (hoặc sử dụng một phần của Resig) để tạo các phương thức trợ giúp đơn giản, ngắn gọn.


bạn có thể thêm một liên kết đến một tài nguyên mô tả việc currying JS là gì không? một hướng dẫn hoặc một bài viết blog sẽ là tuyệt vời.
Eric Schoonover

2
svendtofte.com đã bị kéo dài nhưng nếu bạn bỏ qua toàn bộ phần từ "Một khóa học sự cố trong ML" và bắt đầu lại ở "Cách viết JavaScript bị quấy rầy" thì nó trở thành một giới thiệu tuyệt vời về cà ri trong js.
danio

1
Đây là điểm khởi đầu tốt để hiểu cà ri và ứng dụng một phần thực sự là gì: slid.es/gsklee/feftal-programming-in-5-minutes
gsklee

1
Các liên kết đến svendtofte.comvẻ là đã chết - tìm thấy nó trên máy Wayback dù tại web.archive.org/web/20130616230053/http://www.svendtofte.com/... Xin lỗi, blog.morrisjohns.com/javascript_closures_for_dummies có vẻ là xuống quá
phatskat

1
BTW, phiên bản một phần của Resig bị thiếu (chắc chắn không phải là "trên tiền") ở chỗ nó có thể sẽ thất bại nếu một trong các đối số được khởi tạo trước ("bị cấm") được đưa ra giá trị không xác định . Bất cứ ai quan tâm đến chức năng cà ri tốt nên lấy bản gốc từ funcitonal.js của Oliver Steele , vì nó không có vấn đề đó.
RobG

Câu trả lời:


35

@Hank Gay

Đáp lại bình luận của EmbiggensTheMind:

Tôi không thể nghĩ ra một trường hợp trong đó việc sử dụng cà ri của chínhbyby là hữu ích trong JavaScript; nó là một kỹ thuật để chuyển đổi các cuộc gọi hàm với nhiều đối số thành chuỗi các lệnh gọi hàm với một đối số cho mỗi cuộc gọi, nhưng JavaScript hỗ trợ nhiều đối số trong một lệnh gọi hàm.

Trong JavaScript, và tôi giả sử hầu hết các ngôn ngữ thực tế khác (không phải tính toán lambda) Mặc dù vậy, phần mềm thường được liên kết với ứng dụng một phần. John Resig giải thích điều đó tốt hơn , nhưng ý chính là có một số logic sẽ được áp dụng cho hai hoặc nhiều đối số và bạn chỉ biết (các) giá trị cho một số đối số đó.

Bạn có thể sử dụng một phần ứng dụng / currying để sửa các giá trị đã biết đó và trả về một hàm chỉ chấp nhận các ẩn số, sẽ được gọi sau khi bạn thực sự có các giá trị bạn muốn vượt qua. Điều này cung cấp một cách tiện lợi để tránh lặp lại chính bạn khi bạn đã gọi đi gọi lại cùng một JavaScript với tất cả các giá trị giống nhau nhưng chỉ một. Để đánh cắp ví dụ của John:

String.prototype.csv = String.prototype.split.partial(/,\s*/);
var results = "John, Resig, Boston".csv();
alert( (results[1] == "Resig") + " The text values were split properly" );


6
Đây thực sự là một câu trả lời tồi. Currying không có gì để làm với ứng dụng một phần. Currying cho phép thành phần chức năng. Thành phần chức năng cho phép tái sử dụng chức năng. Tái sử dụng các chức năng làm tăng khả năng bảo trì mã. Nó là dễ dàng!

2
@ftor thưa ông, bạn là một câu trả lời rất tệ. Currying rõ ràng là về làm cho chức năng ngon hơn. Bạn rõ ràng đã bỏ lỡ điểm.
Callat

112

Đây là một cách sử dụng thú vị và thực tế của currying trong JavaScript sử dụng các bao đóng :

function converter(toUnit, factor, offset, input) {
    offset = offset || 0;
    return [((offset + input) * factor).toFixed(2), toUnit].join(" ");
}

var milesToKm = converter.curry('km', 1.60936, undefined);
var poundsToKg = converter.curry('kg', 0.45460, undefined);
var farenheitToCelsius = converter.curry('degrees C', 0.5556, -32);

milesToKm(10);            // returns "16.09 km"
poundsToKg(2.5);          // returns "1.14 kg"
farenheitToCelsius(98);   // returns "36.67 degrees C"

Điều này phụ thuộc vào một curryphần mở rộng của Function, mặc dù như bạn có thể thấy nó chỉ sử dụng apply(không có gì quá lạ mắt):

Function.prototype.curry = function() {
    if (arguments.length < 1) {
        return this; //nothing to curry with - return function
    }
    var __method = this;
    var args = toArray(arguments);
    return function() {
        return __method.apply(this, args.concat([].slice.apply(null, arguments)));
    }
}

5
Điều đó thật tuyệt! Tôi thấy nó tương tự như câu trích dẫn có nội dung "Lisp là ngôn ngữ lập trình được lập trình"
santiagobasulto

2
Thật thú vị, nhưng ví dụ này dường như không hoạt động. offset+inputsẽ undefined + 1.60936trong milesToKmví dụ của bạn ; mà kết quả trong NaN.
Nathan Long

3
@Nathan - offset không thể không xác định - nó mặc định là 0
AngusC

6
Từ những gì tôi đã đọc (chỉ bây giờ), "cà ri" thường không phải là một phần của các thủ thuật của Hàm, trừ khi bạn đang sử dụng thư viện Prototype hoặc tự thêm nó. Rất mát mẻ, mặc dù.
Roboprog

11
Điều tương tự có thể được thực hiện với phương thức liên kết ES5 (). Bind tạo ra một hàm mới mà khi được gọi sẽ gọi hàm ban đầu với ngữ cảnh của đối số đầu tiên và với chuỗi đối số tiếp theo (trước bất kỳ được truyền cho hàm mới). Vì vậy, bạn có thể làm ... var milesToKm = converter.bind (this, 'km', 1,60936); hoặc var TicketnheitToCelsius = convert.bind (cái này, 'độ C', 0,5556, -32); Đối số đầu tiên, bối cảnh, điều này, không liên quan ở đây để bạn có thể vượt qua không xác định. Tất nhiên, bạn sẽ cần phải tăng nguyên mẫu Hàm cơ bản bằng phương thức liên kết của riêng bạn cho dự phòng không ES5
hacklikecrack

7

Tôi đã tìm thấy các hàm giống với python functools.partialhữu ích hơn trong JavaScript:

function partial(fn) {
  return partialWithScope.apply(this,
    Array.prototype.concat.apply([fn, this],
      Array.prototype.slice.call(arguments, 1)));
}

function partialWithScope(fn, scope) {
  var args = Array.prototype.slice.call(arguments, 2);
  return function() {
    return fn.apply(scope, Array.prototype.concat.apply(args, arguments));
  };
}

Tại sao bạn muốn sử dụng nó? Một tình huống phổ biến mà bạn muốn sử dụng điều này là khi bạn muốn liên kết thistrong một hàm với một giá trị:

var callback = partialWithScope(Object.function, obj);

Bây giờ khi gọi lại được gọi, thistrỏ đến obj. Điều này rất hữu ích trong các tình huống sự kiện hoặc để tiết kiệm một số không gian vì nó thường làm cho mã ngắn hơn.

Currying tương tự như một phần với sự khác biệt mà chức năng mà currying trả về chỉ chấp nhận một đối số (theo như tôi hiểu điều đó).


7

Đồng ý với Hank Gay - Nó cực kỳ hữu ích trong một số ngôn ngữ lập trình chức năng thực sự - bởi vì đó là một phần cần thiết. Ví dụ, trong Haskell, bạn chỉ đơn giản là không thể lấy nhiều tham số cho một hàm - bạn không thể làm điều đó trong lập trình hàm thuần túy. Bạn mất một param tại một thời điểm và xây dựng chức năng của bạn. Trong JavaScript đơn giản là không cần thiết, mặc dù các ví dụ giả định như "trình chuyển đổi". Đây là mã chuyển đổi tương tự, mà không cần currying:

var converter = function(ratio, symbol, input) {
    return (input*ratio).toFixed(2) + " " + symbol;
}

var kilosToPoundsRatio = 2.2;
var litersToUKPintsRatio = 1.75;
var litersToUSPintsRatio = 1.98;
var milesToKilometersRatio = 1.62;

converter(kilosToPoundsRatio, "lbs", 4); //8.80 lbs
converter(litersToUKPintsRatio, "imperial pints", 2.4); //4.20 imperial pints
converter(litersToUSPintsRatio, "US pints", 2.4); //4.75 US pints
converter(milesToKilometersRatio, "km", 34); //55.08 km

Tôi rất mong muốn Douglas Crockford, trong "JavaScript: The Good Parts", đã đưa ra một số đề cập đến lịch sử và việc sử dụng cà ri thực tế hơn là những nhận xét trái ngược của anh ấy. Trong thời gian dài nhất sau khi đọc nó, tôi đã rất bối rối, cho đến khi tôi đang học lập trình Chức năng và nhận ra đó là nơi nó đến.

Sau khi suy nghĩ thêm, tôi khẳng định có một trường hợp sử dụng hợp lệ cho việc sử dụng JavaScript: nếu bạn đang cố gắng viết bằng các kỹ thuật lập trình chức năng thuần túy bằng JavaScript. Có vẻ như một trường hợp sử dụng hiếm.


2
Mã của bạn dễ hiểu hơn nhiều so với Prisoner Zero và nó giải quyết vấn đề tương tự mà không cần cà ri hay bất cứ điều gì phức tạp. Bạn đã có 2 ngón tay cái và anh ấy có gần 100. Hãy tìm hiểu.
DR01D

2

Đó không phải là phép thuật hay bất cứ điều gì ... chỉ là một tốc ký dễ chịu cho các chức năng ẩn danh.

partial(alert, "FOO!") tương đương với function(){alert("FOO!");}

partial(Math.max, 0) tương ứng với function(x){return Math.max(0, x);}

Các cuộc gọi đến một phần ( thuật ngữ MochiKit . Tôi nghĩ rằng một số thư viện khác cung cấp cho các hàm một phương thức .curry giống như vậy) trông đẹp hơn và ít ồn hơn các hàm ẩn danh.


1

Đối với các thư viện sử dụng nó, luôn luôn có chức năng .

Khi nào nó hữu ích trong JS? Có lẽ cùng một lúc nó hữu ích trong các ngôn ngữ hiện đại khác, nhưng lần duy nhất tôi có thể thấy bản thân mình sử dụng nó là kết hợp với ứng dụng một phần.


Cảm ơn Hank - xin vui lòng bạn có thể mở rộng khi nó hữu ích nói chung?
Dave Nolan

1

Tôi có thể nói rằng, rất có thể, tất cả các thư viện hoạt hình trong JS đang sử dụng currying. Thay vì phải chuyển cho mỗi cuộc gọi một tập hợp các phần tử và hàm bị ảnh hưởng, mô tả cách phần tử hoạt động, đến một hàm bậc cao hơn sẽ đảm bảo tất cả các công cụ thời gian, khách hàng thường dễ dàng phát hành hơn, như một số API công khai chức năng như "slideUp", "fadeIn" chỉ lấy các phần tử làm đối số và đó chỉ là một số hàm được trả về hàm thứ tự cao có tích hợp "chức năng hoạt hình" mặc định.


Tại sao tốt hơn là để cà ri chức năng highup chứ không chỉ đơn giản là gọi nó với một số mặc định?
Dave Nolan

1
Bởi vì nó có tính mô đun cao hơn để có thể tạo ra một "doMathOperation" với phép cộng / nhân / vuông / mô đun / hiệu chỉnh khác theo ý muốn hơn là tưởng tượng tất cả "mặc định" mà chức năng cao hơn có thể hỗ trợ.
gizmo

1

Đây là một ví dụ.

Tôi đang kết hợp một loạt các trường với JQuery để tôi có thể thấy những gì người dùng đang làm. Mã trông như thế này:

$('#foo').focus(trackActivity);
$('#foo').blur(trackActivity);
$('#bar').focus(trackActivity);
$('#bar').blur(trackActivity);

(Đối với người dùng không phải là JQuery, tôi nói rằng bất cứ khi nào một vài trường bị mất hoặc mất tiêu điểm, tôi muốn hàm trackActivity () được gọi. Tôi cũng có thể sử dụng một hàm ẩn danh, nhưng tôi phải sao chép nó 4 lần, vì vậy tôi rút nó ra và đặt tên cho nó.)

Bây giờ hóa ra một trong những lĩnh vực đó cần phải được xử lý khác nhau. Tôi muốn có thể truyền tham số vào một trong những cuộc gọi được chuyển đến cơ sở hạ tầng theo dõi của chúng tôi. Với cà ri, tôi có thể.


1

Các hàm JavaScript được gọi là lamda trong ngôn ngữ chức năng khác. Nó có thể được sử dụng để soạn một api mới (hàm mạnh hơn hoặc phức tạp hơn) dựa trên đầu vào đơn giản của nhà phát triển khác. Curry chỉ là một trong những kỹ thuật. Bạn có thể sử dụng nó để tạo một api đơn giản hóa để gọi một api phức tạp. Nếu bạn là người phát ngôn sử dụng api đơn giản hóa (ví dụ bạn sử dụng jQuery để thực hiện thao tác đơn giản), bạn không cần sử dụng cà ri. Nhưng nếu bạn muốn tạo ra api đơn giản hóa, cà ri là bạn của bạn. Bạn phải viết một khung javascript (như jQuery, mootools) hoặc thư viện, sau đó bạn có thể đánh giá cao sức mạnh của nó. Tôi đã viết một chức năng cà ri nâng cao, tại http://blog.semanticsworks.com/2011/03/enhified-curry-method.html. Bạn không cần phương thức cà ri để thực hiện cà ri, nó chỉ giúp làm cà ri, nhưng bạn luôn có thể thực hiện thủ công bằng cách viết một hàm A () {} để trả về một hàm B () {} khác. Để làm cho nó thú vị hơn, sử dụng hàm B () để trả về một hàm C () khác.


1

Tôi biết chủ đề cũ của nó nhưng tôi sẽ phải chỉ ra cách thức này đang được sử dụng trong các thư viện javascript:

Tôi sẽ sử dụng thư viện lodash.js để mô tả các khái niệm này một cách cụ thể.

Thí dụ:

var fn = function(a,b,c){ 
return a+b+c+(this.greet || '); 
}

Ứng dụng một phần:

var partialFnA = _.partial(fn, 1,3);

Cà ri:

var curriedFn = _.curry(fn);

Ràng buộc:

var boundFn = _.bind(fn,object,1,3 );//object= {greet: ’!'}

sử dụng:

curriedFn(1)(3)(5); // gives 9 
or 
curriedFn(1,3)(5); // gives 9 
or 
curriedFn(1)(_,3)(2); //gives 9


partialFnA(5); //gives 9

boundFn(5); //gives 9!

Sự khác biệt:

sau khi curry chúng ta có một hàm mới không có tham số nào bị ràng buộc trước.

sau khi ứng dụng một phần, chúng ta có một hàm được liên kết với một số tham số trước.

trong ràng buộc, chúng ta có thể liên kết một bối cảnh sẽ được sử dụng để thay thế 'cái này', nếu không bị ràng buộc mặc định của bất kỳ chức năng nào sẽ là phạm vi cửa sổ.

Khuyên bạn: Không cần phải phát minh lại bánh xe. Ứng dụng một phần / ràng buộc / currying có liên quan rất nhiều. Bạn có thể thấy sự khác biệt ở trên. Sử dụng ý nghĩa này ở bất cứ đâu và mọi người sẽ nhận ra những gì bạn đang làm mà không có vấn đề gì trong việc hiểu cộng với bạn sẽ phải sử dụng ít mã hơn.


0

Tôi đồng ý rằng đôi khi bạn muốn làm cho quả bóng lăn bằng cách tạo một hàm giả sẽ luôn có giá trị của đối số đầu tiên được điền vào. May mắn thay, tôi đã bắt gặp một thư viện JavaScript hoàn toàn mới có tên là jPaq (h ttp: // jpaq.org/ ) cung cấp chức năng này. Điều tốt nhất về thư viện là thực tế là bạn có thể tải xuống bản dựng của riêng bạn chỉ chứa mã mà bạn sẽ cần.



0

Chỉ muốn thêm một số tài nguyên cho Functional.js:

Bài giảng / hội thảo giải thích một số ứng dụng http://www.youtube.com/watch?v=HAcN3JyQoyY

Thư viện Functional.js đã cập nhật: https://github.com/loop-recur/FeftalJS Một số người trợ giúp tốt (xin lỗi mới ở đây, không có tiếng tăm: p): / loop-recur / PreludeJS

Gần đây tôi đã sử dụng thư viện này rất nhiều để giảm sự lặp lại trong thư viện trợ giúp khách hàng của IRs js. Đó là công cụ tuyệt vời - thực sự giúp dọn dẹp và đơn giản hóa mã.

Ngoài ra, nếu hiệu suất trở thành một vấn đề (nhưng lib này khá nhẹ), thật dễ dàng để viết lại bằng hàm gốc.


0

Bạn có thể sử dụng liên kết riêng cho giải pháp một dòng nhanh chóng

function clampAngle(min, max, angle) {
    var result, delta;
    delta = max - min;
    result = (angle - min) % delta;
    if (result < 0) {
        result += delta;
    }
    return min + result;
};

var clamp0To360 = clampAngle.bind(null, 0, 360);

console.log(clamp0To360(405)) // 45


0

Một cú đâm khác vào nó, từ làm việc với những lời hứa.

(Tuyên bố miễn trừ trách nhiệm: JS noob, đến từ thế giới Python. Ngay cả ở đó, cà ri không được sử dụng nhiều, nhưng đôi khi nó có thể có ích.

Đầu tiên, tôi bắt đầu với một cuộc gọi ajax. Tôi có một số xử lý cụ thể để thực hiện thành công, nhưng khi thất bại, tôi chỉ muốn cung cấp cho người dùng phản hồi rằng việc gọi một cái gì đó dẫn đến một số lỗi . Trong mã thực tế của tôi, tôi hiển thị phản hồi lỗi trong bảng bootstrap, nhưng tôi chỉ sử dụng đăng nhập ở đây.

Tôi đã sửa đổi url trực tiếp của mình để làm cho điều này thất bại.

function ajax_batch(e){
    var url = $(e.target).data("url");

    //induce error
    url = "x" + url;

    var promise_details = $.ajax(
        url,
        {
            headers: { Accept : "application/json" },
            // accepts : "application/json",
            beforeSend: function (request) {
                if (!this.crossDomain) {
                    request.setRequestHeader("X-CSRFToken", csrf_token);
                }
        },
        dataType : "json",
        type : "POST"}
    );
    promise_details.then(notify_batch_success, fail_status_specific_to_batch);
}

Bây giờ, ở đây để nói với người dùng rằng một lô thất bại, tôi cần phải viết thông tin đó trong trình xử lý lỗi, bởi vì tất cả những gì nó nhận được là một phản hồi từ máy chủ.

Tôi vẫn chỉ có sẵn thông tin tại thời điểm mã hóa - trong trường hợp của tôi, tôi có một số lô có thể, nhưng tôi không biết cái nào bị lỗi khi phân tích phản hồi của máy chủ về url bị lỗi.

function fail_status_specific_to_batch(d){
    console.log("bad batch run, dude");
    console.log("response.status:" + d.status);
}

Hãy làm nó. Bảng điều khiển đầu ra là:

bảng điều khiển:

bad batch run, dude utility.js (line 109) response.status:404

Bây giờ, chúng ta hãy thay đổi mọi thứ một chút và sử dụng một trình xử lý thất bại chung tái sử dụng, nhưng cũng là một trong đó là cà ri trong thời gian chạy với cả tiếng-at-mã-thời gian gọi bối cảnh và thông tin thời gian chạy có sẵn từ sự kiện.

    ... rest is as before...
    var target = $(e.target).text();
    var context = {"user_msg": "bad batch run, dude.  you were calling :" + target};
    var contexted_fail_notification = curry(generic_fail, context); 

    promise_details.then(notify_batch_success, contexted_fail_notification);
}

function generic_fail(context, d){
    console.log(context);
    console.log("response.status:" + d.status);
}

function curry(fn) {
     var slice = Array.prototype.slice,
        stored_args = slice.call(arguments, 1);
     return function () {
        var new_args = slice.call(arguments),
              args = stored_args.concat(new_args);
        return fn.apply(null, args);
     };
}

bảng điều khiển:

Object { user_msg="bad batch run, dude. you were calling :Run ACL now"} utility.js (line 117) response.status:404 utility.js (line 118)

Tổng quát hơn, được đưa ra mức độ sử dụng gọi lại rộng rãi trong JS, currying có vẻ như là một công cụ khá hữu ích.

https://javascriptweblog.wordpress.com/2010/04/05/curry-cooking-up-tastier-fifts/ http://www.drdobbs.com/open-source/currying-and-partial-fifts-in- javasc / 231001821? pgno = 2

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.