Pure function là gì và tại sao nên sử dụng nó trong JavaScript?

Hàm thuần khiết “Pure” function là gì?

Một hàm thuần khiết không phụ thuộc và không bị thay đổi trạng thái vào các biến bên ngoài phạm vi hoạt động của nó.

Điều đó cũng có nghĩa là, một hàm thuần khiết luôn luôn trả về kết quả giống nhau khi tham số truyền vào giống nhau. Nó thực thi không phụ thuộc vào trạng thái của hệ thống.

Pure functions là một nguyên tắc trong functional programing.

Ví dụ:

var values = { a: 1 };

function impureFunction ( items ) {
  var b = 1;

  items.a = items.a * b + 2;

  return items.a;
}

var c = impureFunction( values );
//Ở đây value.a có giá trị 3, impureFuntion thay đổi nó

Ở đây các bạn có thể thấy biến values là biến ở bên ngoái ‘{}’ là scope của impureFuntion đã bị thay đổi giá trị nên nó là hàm không thuần khiết.

var values = { a: 1 };

function pureFunction ( a ) {
  var b = 1;

  a = a * b + 2;

  return a;
}

var c = pureFunction( values.a );
//value.a không bị chỉnh sửa, nó vẫn là 1

Chúng ta đơn giản chỉnh lại tham số của hàm và chỉ thao tác với tham số đó, vậy nó sẽ không chỉnh sửa bất kỳ thứ gì bên ngoài scope.

var values = { a: 1 };
var b = 1;

function impureFunction ( a ) {
  a = a * b + 2;

  return a;
}

var c = impureFunction( values.a );
//Ở đây rõ ràng là giá trị của ‘c’ sẽ phụ thuộc vào giá trị của ‘b’
//Việc một trạng thái(“biến”) có thể thai đổi bất kỳ ở đâu thì sau này khi ứng dụng phát triển thì việc debug rất phức tạp.

Ở đây thì b không nằm trong scope của impureFuntion.

var values = { a: 1 };
var b = 1;

function pureFunction ( a, c ) {
  a = a * c + 2;

  return a;
}

var c = pureFunction( values.a, b );
//Ở đây đơn giản chúng ta thêm một tham số nữa nó sẽ giải quyết được vấn đề ở trê

Thực tế nó là gì?

Cùng nghiên cứu đoạn mã dưới đây:

var getMinQuantity = function getMinQuantity ( name ) {
  // một pure function sẽ trả về một giá trị.
};

Nghiên cứu các đoạn mã sau:
var popover = {

  // A bunch of code…

  addQuantityText: function ( quantity ) {
    var quantityTextOptions = {
      namespace: "quantity",
      initialChildIndex: 2,
      quantity: quantity
    };

    try {
      this.formatQuantityText( quantityTextOptions );
    } catch ( err ) {
      console.log( "Couldn't add quantity text!" );
    }
  },

  formatQuantityText: function ( options ) {
    if ( !this.$$boxContainer ) {
      throw new Error( "$$boxContainer is not configured" );
    }

    var namespace = options.namespace || "quantity";
    var quantity = options.quantity || 0;
    var initialChildIndex = options.initialChildIndex || 0;

    var $$quantity = new Canvas(); // implementation details hidden
    $$quantity.name = namespace;
    $$quantity.value = quantity;
    this.setQuantityTextColor( $$quantity );

    this.$$boxContainer.addChild( $$quantity, initialChildIndex );

    return $$quantity;
  },

  setQuantityTextColor: function ( $$quantity ) {
    if ( !$$quantity ) return;

    var minQuantity = getMinQuantity( $$quantity.name );
    var quantity = $$quantity.value || minQuantity;
    var hasEnoughQuantity = (quantity >= minQuantity);

    $$quantity.color = (hasEnoughQuantity) ? "green" : "red";
  },

};
  • Ở trên chúng ta có 3 functions: addQuantityText(), formatQuantityText() and setQuantityTextColor() và tất cả chúng đều là impure.

  • Trong đoạn mã trên, addQuantityText() là phương thức sử dụng khi bạn muốn hiển thị số các phần tử trong boxContrainer. Điều gì xảy ra nếu biến $$quantity thay đổi.

  • Ở hàm formatQuantityText chúng ta truyền $$quantity vào hàm setQuantityTextColor nó đã thay đổi một biến mà bên ngoài scope của nó, và việc thay đổi cách biến trạng thái $$boxContainer đặt ở nhiều chỗ sẽ gây việc debug sau này rất khó khăn.

Dưới đây chúng ta sẽ sửa lại như sau: 

var popover = {

  //Tất cả việc chỉnh sửa các biến trạng thái đều được đặt ở đây
  addQuantityText: function ( quantity ) {
    if ( !this.$$boxContainer ) {
      throw new Error( "$$boxContainer is not configured" );
    }

    var quantityTextOptions = {
      namespace: "quantity",
      quantity: quantity
    };
    var $$quantity = this.formatQuantityText( quantityTextOptions );

    this.$$boxContainer.addChild( $$quantity, 2 );
  },

  //Ở hàm này nó sẽ không thay đổi biến trạng thái nào cả
  //Nó chỉ gọi đến pure function khác để thay đổi thuộc tính của biến cục bộ 
  //sau đó trả lại cho hàm gọi nó
  formatQuantityText: function ( options ) {
    var namespace = options.namespace || "quantity";
    var quantity = options.quantity || 0;

    var $$quantity = new Canvas(); 
    $$quantity.name = namespace;
    $$quantity.value = quantity;
    $$quantity.color = this.getQuantityTextColor( quantity, namespace );

    return $$quantity;
  },

  //Hàm này chỉ trả lại màu chứ không can thiệp vào biến quantity như trước nữa.
  getQuantityTextColor: function ( quantity, namespace ) {
    var minQuantity = getMinQuantity( namespace );
    var hasEnoughQuantity = (quantity && quantity >= minQuantity);

    return (hasEnoughQuantity) ? "green" : "red";
  },

};

 

Những điều thay đổi:

  • Hàm getQuantityTextColor() thay thế cho setQuantityTextColor()

  • Phương thức này sẽ trả về màu chứ nó sẽ không gán giá trị thẳng vào biến nữa.

  • Thao tác hoàn toàn trong scope của nó.

  • Tách tiệc tạo $$quantity trong formatQuantityText() nó vẫn hoạt đụng đúng scope và trả về giá trị.

  • Tất cả việc thay đổi biến trạng thái được thao tác trong addQuantityText với đúng tên của nó, ở đây chúng ta đã cố gắng tạo ra tối đa và thao tác các pure methods .

Lợi ích của pure functions:

Nó không thay đổi trạng thái mà nằm bên ngoài scope của nó. Vì vậy bạn chỉ cần để ý giá trị khi gọi pure function trả về là gì.

Điều đó có nghĩa là bạn lúc nào cũng chỉ cần quan tâm đến input và output ra là gì. Nhưng với một chương trình thì việc có những hàm impure là không thể tránh khỏi nên chúng ta nên cố gắng hạn chế việc tạo ra các inpure functions ít nhất có thể vì bạn sẽ chắc chắn dễ dàng bảo trì dự án hơn.

Tóm tắt:

  • Pure function chỉ thao tác trên tham số truyền vào

  • Ít nhất 1 tham số

  • Luôn trả về giá trị

  • Trả về giá trị không đổi khi tham số không đổi

  • Không có side effects(tác dụng phụ)