Currying in Javascript

Curry là gì

Cà-ri là một thuật ngữ tổng quát trong tiếng Anh (tiếng Anh là curry, số nhiều là curries) và nhiều ngôn ngữ khác, chủ yếu được sử dụng trong văn hóa phương Tây để chỉ một loạt các món ăn hầm cay hoặc ngọt có thành phần chính là bột cà ri, nổi tiếng nhất trong Ẩm thực Ấn Độ, Thái, và Nam Á.

Nhưng lần này thứ curry được đề cập đến là 1 khái niệm có thể được ứng dụng trong nhiều ngôn ngữ lập trình. Vậy cụ thể thì nó là cái gì ?

Hãy xem xét ví dụ dưới đây.

Giả sử bạn muốn viết 1 hàm max trả về số lớn hơn trong 2 số. Câu chuyện rất đơn giản, thường thì bạn sẽ tạo 1 hàm kiểu thế này:

function max(x, y){
    return x > y ? x : y;
}

Tuy nhiên bạn cũng có thể viết 1 hàm _max có chức năng tương tự kiểu như thế này

function _max(x){
    return function(y){
        return x > y ? x : y;
    }
}

Thực ra giá trị trả về của hàm _max không phải là 1 số mà là 1 function, function đó nhận đầu vào là 1 số, so sánh số đó với 1 số x cho trước và trả về số lớn hơn trong 2 số. Với cách viết này ta có thể thấy kết cấu function trong function. Cách sử dụng khi gọi hàm cũng có sự khác biệt. Ví dụ để so sánh 1 và 2 ta có thể dùng _max(1)(2) thay vì max(1, 2)

Cách định nghĩa function chỉ có 1 tham số đầu vào thay vì 2 tham số như trên chính là "curry", "currying". Tất nhiên chúng ta có thể "currying" 1 function có nhiều hơn 2 tham số đầu vào

Curry có ngon không

Cá nhân thì mình thấy curry kiểu Nhật ăn cũng được còn curry kiểu Ấn ăn không thích lắm =))

Còn nói về cách thức currying được đề cập ở trên thì ấn tượng ban đầu là code dài hơn, cấu trúc loằng ngoằng hơn, thêm lắm dấu ngoặc hơn trong khi vẫn chỉ thực hiện được chức năng như vậy, có vẻ cũng không ngon lắm.

Vậy cách viết theo kiểu currying này rốt cục có lợi ích gì không

Hồi học đại học có cái trò thi trắc nghiệm trả lời sai không những không được điểm mà còn bị trừ điểm luôn câu sai đó để chống các thành phần thần rùa số đỏ. Nhưng kết cục là khi trả bài có những đứa bị âm điểm luôn. Đến khi vào sổ thì thôi thầy đành làm tròn lên thành 0 vậy (haiz)

Ví dụ list điểm của 1 danh sách học sinh như sau

var raw_points = [5, 8, -2, 3, -1]

Để chuyển đống điểm thô này thành điểm nhập vào sổ đơn giản ta quét qua danh sách điểm, so sánh điểm đó với 0, nếu > 0 thì giữ nguyên ngược lại thì cho thành 0. Ta có thể tận dụng hàm max đã giới thiệu ở phần trước kết hợp với dùng map cho array

var final_points = raw_points.map(function(x){ return max(0, x) });

Cách tính như trên dùng 1 anonymous function trả về giá trị lớn hơn khi so sánh 1 số với 0.

Trả về function ?? Có gì đó quen quen. Chúng ta có thể sử dụng cách định nghĩa function kiểu currying ở trên và dùng _max(0) thay cho anonymous function vừa rồi

var final_points = raw_points.map(_max(0));

(ngon) hóa ra lúc nấu ăn khổ 1 tí thì mới có món ngon để mà nếm

Công thức chuẩn

Nếu curry ngon vậy thì chúng ta hãy xây dựng nên 1 công thức chuẩn để thuận tiện cho việc nấu curry

Giá mà có 1 function chuyển nguyên liệu là những function chưa được currying thành curry là tuyệt.

var currier = function (fn) {
  var args = Array.prototype.slice.call(arguments, 1);

  return function () {
    return fn.apply(this, args.concat(
      Array.prototype.slice.call(arguments, 0)
    ));
  }
}

Ý tưởng cho currier sẽ là viết 1 function trong đó tham số đầu tiên là function cần được currying, các tham số tiếp theo là 1 phần trong danh sách tham số của function đó, khi tính toán sẽ gộp các tham số của function gốc với các tham số được gọi khi dùng currier.

Demo cho việc currying function Math.max

Math.max(1,3,4)
-> 4
currying_max = currier(Math.max, 1, 4)
-> () {
    return fn.apply(this, args.concat(
      Array.prototype.slice.call(arguments, 0)
    ));
  }
currying_max(3)
-> 4
currying_max(5)
-> 5
currying_max(3, 8)
-> 8

Kết

Sử dụng currying function có những điểm mạnh và điểm yếu như sau

Điểm mạnh

  • Có thể sử dụng linh hoạt function trong nhiều tình huống

Điểm yếu

  • Code dài hơn, ban đầu có thể là khó hiểu hơn